From 8dae1eb84a8c854f67728c985448fba65656569c Mon Sep 17 00:00:00 2001 From: Agustin Zubiaga Date: Mon, 09 Apr 2012 23:56:12 +0000 Subject: Initial commit Signed-off-by: Agustin Zubiaga --- diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..33d985e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.mo +*.xo +*.pyc +*.pyo diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..7bc4d39 --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ +Hugo Ruscitti diff --git a/GFXLIB_LICENSE b/GFXLIB_LICENSE new file mode 100644 index 0000000..3cb8184 --- /dev/null +++ b/GFXLIB_LICENSE @@ -0,0 +1,263 @@ + __ _ _ _ + / _| | (_) | + __ _| |___ _| |_| |__ + / _` | _\ \/ / | | '_ \ +| (_| | | > <| | | |_) | + \__, |_| /_/\_\_|_|_.__/ + __/ | + |___/ +---------------------------------------------------------------------- +Product : gfxlib-fuzed.zip +Website : http://www.spicypixel.net +Author : Marc Russell +Released: 10th January 2008 +---------------------------------------------------------------------- + +What is this? +------------- +gfxlib-fuzed is a package of free art assets to be used under the terms of this document. It is available to game developers and hobbyists alike. + +Contents +-------- +The contents of the gfxlib-fuzed ZIP file are as follows. + +Directories + * Backgrounds - 4 Graphic tilesets, level data and level mockup. + * Sprites - Ingame Sprite animations + * GUI - Work In Progress menu artwork and icons + +Usage License & Restrictions +---------------------------- +gfxlib is distributed under the "Common Public License Version 1.0." +The terms of which are given below. If you do not understand the terms of the license please refer to a solicitor. It should however, be relatively clear how this package can be used. + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS COMMON +PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF +THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + + a) in the case of the initial Contributor, the initial code and + documentation distributed under this Agreement, and + + b) in the case of each subsequent Contributor: + + i) changes to the Program, and + + ii) additions to the Program; + + where such changes and/or additions to the Program originate from + and are distributed by that particular Contributor. A Contribution + 'originates' from a Contributor if it was added to the Program by + such Contributor itself or anyone acting on such Contributor's + behalf. Contributions do not include additions to the Program which: + (i) are separate modules of software distributed in conjunction with + the Program under their own license agreement, and (ii) are not + derivative works of the Program. + +"Contributor" means any person or entity that distributes the Program. + +"Licensed Patents " mean patent claims licensable by a Contributor which +are necessarily infringed by the use or sale of its Contribution alone +or when combined with the Program. + +"Program" means the Contributions distributed in accordance with this +Agreement. + +"Recipient" means anyone who receives the Program under this Agreement, +including all Contributors. + +2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free copyright + license to reproduce, prepare derivative works of, publicly display, + publicly perform, distribute and sublicense the Contribution of such + Contributor, if any, and such derivative works, in source code and + object code form. + + b) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free patent + license under Licensed Patents to make, use, sell, offer to sell, + import and otherwise transfer the Contribution of such Contributor, + if any, in source code and object code form. This patent license + shall apply to the combination of the Contribution and the Program + if, at the time the Contribution is added by the Contributor, such + addition of the Contribution causes such combination to be covered + by the Licensed Patents. The patent license shall not apply to any + other combinations which include the Contribution. No hardware per + se is licensed hereunder. + + c) Recipient understands that although each Contributor grants the + licenses to its Contributions set forth herein, no assurances are + provided by any Contributor that the Program does not infringe the + patent or other intellectual property rights of any other entity. + Each Contributor disclaims any liability to Recipient for claims + brought by any other entity based on infringement of intellectual + property rights or otherwise. As a condition to exercising the + rights and licenses granted hereunder, each Recipient hereby assumes + sole responsibility to secure any other intellectual property rights + needed, if any. For example, if a third party patent license is + required to allow Recipient to distribute the Program, it is + Recipient's responsibility to acquire that license before + distributing the Program. + + d) Each Contributor represents that to its knowledge it has + sufficient copyright rights in its Contribution, if any, to grant + the copyright license set forth in this Agreement. + +3. REQUIREMENTS + +A Contributor may choose to distribute the Program in object code form +under its own license agreement, provided that: + + a) it complies with the terms and conditions of this Agreement; and + + b) its license agreement: + + i) effectively disclaims on behalf of all Contributors all + warranties and conditions, express and implied, including warranties + or conditions of title and non-infringement, and implied warranties + or conditions of merchantability and fitness for a particular + purpose; + + ii) effectively excludes on behalf of all Contributors all liability + for damages, including direct, indirect, special, incidental and + consequential damages, such as lost profits; + + iii) states that any provisions which differ from this Agreement are + offered by that Contributor alone and not by any other party; and + + iv) states that source code for the Program is available from such + Contributor, and informs licensees how to obtain it in a reasonable + manner on or through a medium customarily used for software + exchange. + +When the Program is made available in source code form: + + a) it must be made available under this Agreement; and + + b) a copy of this Agreement must be included with each copy of the + Program. + +Contributors may not remove or alter any copyright notices contained +within the Program. + +Each Contributor must identify itself as the originator of its +Contribution, if any, in a manner that reasonably allows subsequent +Recipients to identify the originator of the Contribution. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities +with respect to end users, business partners and the like. While this +license is intended to facilitate the commercial use of the Program, the +Contributor who includes the Program in a commercial product offering +should do so in a manner which does not create potential liability for +other Contributors. Therefore, if a Contributor includes the Program in +a commercial product offering, such Contributor ("Commercial +Contributor") hereby agrees to defend and indemnify every other +Contributor ("Indemnified Contributor") against any losses, damages and +costs (collectively "Losses") arising from claims, lawsuits and other +legal actions brought by a third party against the Indemnified +Contributor to the extent caused by the acts or omissions of such +Commercial Contributor in connection with its distribution of the +Program in a commercial product offering. The obligations in this +section do not apply to any claims or Losses relating to any actual or +alleged intellectual property infringement. In order to qualify, an +Indemnified Contributor must: a) promptly notify the Commercial +Contributor in writing of such claim, and b) allow the Commercial +Contributor to control, and cooperate with the Commercial Contributor +in, the defense and any related settlement negotiations. The Indemnified +Contributor may participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial +product offering, Product X. That Contributor is then a Commercial +Contributor. If that Commercial Contributor then makes performance +claims, or offers warranties related to Product X, those performance +claims and warranties are such Commercial Contributor's responsibility +alone. Under this section, the Commercial Contributor would have to +defend claims against the other Contributors related to those +performance claims and warranties, and if a court requires any other +Contributor to pay any damages as a result, the Commercial Contributor +must pay those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED +ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, +EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES +OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR +A PARTICULAR PURPOSE. Each Recipient is solely responsible for +determining the appropriateness of using and distributing the Program +and assumes all risks associated with its exercise of rights under this +Agreement, including but not limited to the risks and costs of program +errors, compliance with applicable laws, damage to or loss of data, +programs or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR +ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING +WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR +DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED +HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of +the remainder of the terms of this Agreement, and without further action +by the parties hereto, such provision shall be reformed to the minimum +extent necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against a Contributor with +respect to a patent applicable to software (including a cross-claim or +counterclaim in a lawsuit), then any patent licenses granted by that +Contributor to such Recipient under this Agreement shall terminate as of +the date such litigation is filed. In addition, if Recipient institutes +patent litigation against any entity (including a cross-claim or +counterclaim in a lawsuit) alleging that the Program itself (excluding +combinations of the Program with other software or hardware) infringes +such Recipient's patent(s), then such Recipient's rights granted under +Section 2(b) shall terminate as of the date such litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it fails +to comply with any of the material terms or conditions of this Agreement +and does not cure such failure in a reasonable period of time after +becoming aware of such noncompliance. If all Recipient's rights under +this Agreement terminate, Recipient agrees to cease use and distribution +of the Program as soon as reasonably practicable. However, Recipient's +obligations under this Agreement and any licenses granted by Recipient +relating to the Program shall continue and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, +but in order to avoid inconsistency the Agreement is copyrighted and may +only be modified in the following manner. The Agreement Steward reserves +the right to publish new versions (including revisions) of this +Agreement from time to time. No one other than the Agreement Steward has +the right to modify this Agreement. IBM is the initial Agreement +Steward. IBM may assign the responsibility to serve as the Agreement +Steward to a suitable separate entity. Each new version of the Agreement +will be given a distinguishing version number. The Program (including +Contributions) may always be distributed subject to the version of the +Agreement under which it was received. In addition, after a new version +of the Agreement is published, Contributor may elect to distribute the +Program (including its Contributions) under the new version. Except as +expressly stated in Sections 2(a) and 2(b) above, Recipient receives no +rights or licenses to the intellectual property of any Contributor under +this Agreement, whether expressly, by implication, estoppel or +otherwise. All rights in the Program not expressly granted under this +Agreement are reserved. + +This Agreement is governed by the laws of the State of New York and the +intellectual property laws of the United States of America. No party to +this Agreement will bring a legal action under this Agreement more than +one year after the cause of action arose. Each party waives its rights +to a jury trial in any resulting litigation. + diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..acebb6e --- /dev/null +++ b/INSTALL @@ -0,0 +1,63 @@ +Instalación +=========== + +Existen 3 formas de instalar la biblioteca, así +que veremos cada una por separado. + +Instalación fácil, para cada sistema operativo +---------------------------------------------- + +La forma mas sencilla de instalar pilas es +siguiendo alguno de los tutoriales del sitio +web de pilas: + +http://www.pilas-engine.com.ar/documentacion + + +Instalación para distribuir con un juego +---------------------------------------- + +Si quieres que la biblioteca esté en un juego pero +no quieres instalarla en cada equipo, puedes +simplemente copiar todo el contenido del directorio +``pilas`` dentro del directorio del juego. + +Entonces, cuando ejecutes una sentencia +como ``import pilas``, la biblioteca se +leerá desde el directorio. + + +Instalación luego de la descarga +-------------------------------- + +Si realizas videojuegos, pero no quieres tener la biblioteca +como directorio físico en todos y cada uno de los juegos +que realices, una buena idea es instalar la biblioteca en +tu sistema. + +Primero tienes que instalar ``setuptools`` usando el +siguiente comando:: + + sudo apt-get install python-setuptools + +Luego, ingresa en el directorio en donde se encuentra +tu copia de pilas y ejecuta un comando cómo:: + + sudo python setup.py install + +o bien:: + + sudo python setup.py develop + +En el primer caso la biblioteca se instala y pasa +a estar completamente instalada en tu sistema. No importa +que borres el directorio que has descargado, la biblioteca +quedará instalada en un directorio del sistema para bibliotecas +de python. + +El segundo caso es el favorito por los desarrolladores, porque +te permite actualizar mas fácilmente la biblioteca. Ya que +no está complemente instalada en el equipo, si no que hace +referencia al directorio en donde se ha descargado pilas. Si actualizas +el directorio origen, los cambios se reflejarán en +tu sistema sin necesidad de repetir el comando de instalación. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..65c5ca8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..29d2c08 --- /dev/null +++ b/README.txt @@ -0,0 +1,177 @@ +============ +Pilas Engine +============ + +Pilas es un motor para realizar videojuegos de manera +rápida y sencilla. + +Es una herramienta orientada a programadores casuales +o principiantes, que quiera comenzar a realizar sus +primeros videojuegos. + + +¿Cómo empezar? +============== + +Una buena forma de comenzar con pilas es instalar todo +el kit de desarrollo siguiendo las intrucciones de +nuestra web: http://www.pilas-engine.com.ar + + +Y una vez instalada la biblioteca, se puede invocar +al comando ``pilas -e`` para ver una lista completa +de ejemplos y minijuegos. + + +¿Cómo generar la documentación del proyecto? +============================================ + +Para generar la documentación del proyecto +usamos ``Sphinx``, el sistema de documentación +mas utilizado en los proyectos de python. + +Para generar los archivos PDF o HTML usamos el comando +``make`` dentro del directorio ``doc``. El archivo que +dispara todas las acciones que sphinx sabe hacer están +definidas en el archivo ``Makefile``. + +Recuerda instalar ``sphinx`` antes de continuar, si solo +quieres generara la documentación HTML alcanza con +ejecutar este comando:: + + apt-get install python-sphinx + +y en caso de que quieras generar la documentación PDF:: + + apt-get install texlive-lang-spanish + + +Otros recursos en la web +======================== + +Existen varios sitios web importantes dentro +de proyecto pilas, la web principal +es: + + - http://www.pilas-engine.com.ar + +y los sitios para desarrolladores, donde se encuentran +las tareas y el repositorio de código son: + + - http://www.dev-losersjuegos.com.ar + - http://bitbucket.org/hugoruscitti/pilas + + +También tenemos una lista de correo en +la siguiente dirección: + + - http://groups.google.com/group/pilas-engine + +Dependencias +============ + +- pygame +- pyqt4 +- pybox2d +- python + + +Licencia +======== + +Pilas es software libre, y se distribuye bajo la +licencia GPLv3. + + +Test de unidad +============== + +Para realizar pruebas de unidad estamos usando ``py.test``. Para +ello tienes que instalar una nueva dependencia con el +comando:: + + sudo easy_install pytest + +y luego correr todas las pruebas con los siguientes comandos:: + + make test + +Créditos y agradecimientos +========================== + +Pilas está principalmente desarrollada por Hugo Ruscitti, y +cuenta con varios dibujos realizados por Walter Velazquez. Ambos +fundadores de Losersjuegos (http://www.losersjuegos.com.ar). + +A continuación se incluye una lista de otros desarrolladores +y proyectos que sumaron a pilas: + +PyTweener +--------- + +Las interpolaciones dentro del motor se realizan +mediante la biblioteca pyTweener, distribuida +bajo la licencia M.I.T. por Ben Harling. + +Dispatch +-------- + +El modulo ``pilas.dispatch`` se adoptó del +core de Django, y estába basado en el proyecto pydispatch. + + +PyQt4 +----- + +Todo el manejo multimedia se realiza gracias a la biblioteca +Qt4: + +- http://qt.nokia.com/ + + +pygame +------ + +Como biblioteca multimedia secundaria usamos +pygame, que además nos permite seleccionarla +en casos donde no hay soporte para OpenGL (como +en los equipos OLPC por ejemplo). + +Box2d +----- + +Para el manejo multimedia estamos usando +la biblioteca box2d: + +- http://code.google.com/p/pybox2d/ + +GFXLib +------ + +He utilizado los siguientes gráficos del proyecto +gfxlib: + +- pilas/data/moneda.png + +Estos gráficos se distribuyen bajo la licencia "Common Public License", y +puedes obtener mas información en: http://www.spicypixel.net + +¡ Gracias Marc ! + + +DANC, de lostgarden.com +----------------------- + +En pilas utilizamos algunos gráficos que DANC publicó +en su sitio web: + +- www.lostgarden.com + + +a "arboris" +----------- + +Por sus gráficos de naves que se pueden +descargar desde la siguiente web: + +- http://arboris.deviantart.com/art/Spaceship-sprites-43030167 diff --git a/activity.py b/activity.py new file mode 100644 index 0000000..35933cc --- /dev/null +++ b/activity.py @@ -0,0 +1,98 @@ +# Copyright 2009 Simon Schampijer +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +"""Pilas Activity. Live interpreter to learn programming with games.""" + +import gtk +import logging + +from gettext import gettext as _ + +from subprocess import Popen + +from sugar.activity import activity +from sugar.graphics.toolbarbox import ToolbarBox +from sugar.activity.widgets import ActivityButton +from sugar.activity.widgets import ActivityToolbox +from sugar.activity.widgets import TitleEntry +from sugar.activity.widgets import StopButton +from sugar.activity.widgets import ShareButton + +import os +import sys +import copy + +base = os.environ['SUGAR_BUNDLE_PATH'] +os.chdir(base) + +qtlib = os.path.join(base, 'qt/lib/') +new_env = copy.copy(os.environ) +new_env['LD_LIBRARY_PATH'] = qtlib + +class PilasActivity(activity.Activity): + """Pilas class as specified in activity.info""" + + def __init__(self, handle): + """Set up the Pilas activity.""" + activity.Activity.__init__(self, handle) + + # we do not have collaboration features, + # make the share option insensitive + self.max_participants = 1 + + # toolbar with the new toolbar redesign + toolbar_box = ToolbarBox() + + activity_button = ActivityButton(self) + toolbar_box.toolbar.insert(activity_button, 0) + activity_button.show() + + title_entry = TitleEntry(self) + toolbar_box.toolbar.insert(title_entry, -1) + title_entry.show() + + share_button = ShareButton(self) + toolbar_box.toolbar.insert(share_button, -1) + share_button.show() + + separator = gtk.SeparatorToolItem() + separator.props.draw = False + separator.set_expand(True) + toolbar_box.toolbar.insert(separator, -1) + separator.show() + + stop_button = StopButton(self) + toolbar_box.toolbar.insert(stop_button, -1) + stop_button.show() + + self.set_toolbar_box(toolbar_box) + toolbar_box.show() + + socket = gtk.Socket() + socket.connect("plug-added", self._on_plugged_event) + socket.set_flags(gtk.CAN_FOCUS) + self.set_canvas(socket) + self.set_focus(socket) + socket.show() + + screen_width = gtk.gdk.screen_width() + screen_height = gtk.gdk.screen_height() + + Popen(["python", "pilas_plug.py", str(socket.get_id()), + str(screen_width), str(screen_height)], env=new_env) + + def _on_plugged_event(self, widget): + logging.info("Plug inserted") diff --git a/activity/activity.info b/activity/activity.info new file mode 100644 index 0000000..a5fe296 --- /dev/null +++ b/activity/activity.info @@ -0,0 +1,7 @@ +[Activity] +name = Pilas +activity_version = 1 +bundle_id = org.sugarlabs.Pilas +exec = sugar-activity activity.PilasActivity +icon = pilas-icon +license = LGPLv3 diff --git a/activity/pilas-icon.svg b/activity/pilas-icon.svg new file mode 100644 index 0000000..b450739 --- /dev/null +++ b/activity/pilas-icon.svg @@ -0,0 +1,52 @@ + + + +]> + \ No newline at end of file diff --git a/bin/pilas b/bin/pilas new file mode 100755 index 0000000..6dbf703 --- /dev/null +++ b/bin/pilas @@ -0,0 +1,36 @@ +#!/usr/bin/python +import sys +from optparse import OptionParser +import pilas + +analizador = OptionParser() + +analizador.add_option("-j", "--crearjuego", dest="crearjuego", + action="store_true", default=False, + help="Genera un directorio de videojuego vacio.") + +analizador.add_option("-e", "--ejemplos", dest="ejemplos", + action="store_true", default=False, + help="Mustra varios ejemplos sencillos de pilas") + +analizador.add_option("-t", "--test", dest="test", + action="store_true", default=False, + help="Invoca varias pruebas verificar el funcionamiento de pilas") + +(opciones, argumentos) = analizador.parse_args() + + +if opciones.crearjuego: + pilas.utils.crear_juego() + sys.exit(0) +elif opciones.ejemplos: + pilas.abrir_cargador() + sys.exit(0) +elif opciones.test: + pilas.utils.realizar_pruebas() + sys.exit(0) + +print "Error, no has indicado un parametro para iniciar pilas." +print "Puedes ejecutar el comando 'pilas --help' para ver instrucciones." +sys.exit(1) + diff --git a/pilas/__init__.py b/pilas/__init__.py new file mode 100644 index 0000000..87c2700 --- /dev/null +++ b/pilas/__init__.py @@ -0,0 +1,172 @@ +# -*- encoding: utf-8 -*- +# pilas engine - a video game framework. +# +# copyright 2010 - hugo ruscitti +# license: lgplv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# website - http://www.pilas-engine.com.ar + +mundo = None +bg = None + +import sys +import utils +from mundo import Mundo +import actores +import fondos +import habilidades +import eventos +import sonidos +import colores +import atajos +import escenas +import ejemplos +import interfaz + +__doc__ = """ +Módulo pilas +============ + +Pilas es una biblioteca para facilitar el desarrollo +de videojuegos. Es útil para programadores +principiantes o para el desarrollo de juegos casuales. + +Este módulo contiene las funciones principales +para iniciar y ejecutar la biblioteca. +""" + +if utils.esta_en_sesion_interactiva(): + utils.cargar_autocompletado() + +def iniciar(ancho=640, alto=480, titulo='Pilas', usar_motor='qtgl', + rendimiento=60, modo='detectar', economico=True, + gravedad=(0, -90), pantalla_completa=False): + """ + Inicia la ventana principal del juego con algunos detalles de funcionamiento. + + Ejemplo de invocación: + + >>> pilas.iniciar(ancho=320, alto=240) + + .. image:: images/iniciar_320_240.png + + Parámetros: + + :ancho: el tamaño en pixels para la ventana. + :alto: el tamaño en pixels para la ventana. + :titulo: el titulo a mostrar en la ventana. + :usar_motor: el motor multimedia a utilizar, puede ser 'qt' o 'qtgl'. + :rendimiento: cantidad de cuadros por segundo a mostrar. + :modo: si se utiliza modo interactivo o no. + :economico: si tiene que evitar consumir muchos recursos de procesador + :gravedad: el vector de aceleracion para la simulacion de fisica. + :pantalla_completa: si debe usar pantalla completa o no. + + """ + + global mundo + + motor = __crear_motor(usar_motor) + mundo = Mundo(motor, ancho, alto, titulo, rendimiento, economico, gravedad, pantalla_completa) + escenas.Normal(colores.grisclaro) + + +def ejecutar(ignorar_errores=False): + """Pone en funcionamiento las actualizaciones y dibujado. + + Esta función es necesaria cuando se crea un juego + en modo ``no-interactivo``.""" + mundo.ejecutar_bucle_principal(ignorar_errores) + +def terminar(): + """Finaliza la ejecución de pilas y cierra la ventana principal.""" + mundo.terminar() + +def ver(objeto, imprimir=True, retornar=False): + """Imprime en pantalla el codigo fuente asociado a un objeto o elemento de pilas.""" + import inspect + + try: + codigo = inspect.getsource(objeto.__class__) + except TypeError: + codigo = inspect.getsource(objeto) + + if imprimir: + print codigo + + if retornar: + return codigo + +def version(): + """Retorna el número de version de pilas.""" + import pilasversion + + return pilasversion.VERSION + +def __crear_motor(usar_motor): + """Genera instancia del motor multimedia en base a un nombre. + + Esta es una función interna y no debe ser ejecutada + excepto por el mismo motor pilas.""" + + if usar_motor == 'qt': + from motores import motor_qt + motor = motor_qt.Qt() + elif usar_motor == 'qtgl': + from motores import motor_qt + motor = motor_qt.QtGL() + else: + print "El motor multimedia seleccionado (%s) no esta disponible" %(usar_motor) + print "Las opciones de motores que puedes probar son 'qt' y 'qtgl'." + sys.exit(1) + + return motor + +def reiniciar(): + """Elimina todos los actores y vuelve al estado inicial.""" + actores.utils.eliminar_a_todos() + mundo.reiniciar() + +anterior_texto = None + +def avisar(mensaje): + """Emite un mensaje en la ventana principal. + + Este mensaje aparecerá en la parte inferior de la pantalla, por + ejemplo: + + >>> pilas.avisar("Use la tecla para terminar el programa") + """ + global anterior_texto + izquierda, derecha, arriba, abajo = utils.obtener_bordes() + + if anterior_texto: + anterior_texto.eliminar() + + texto = actores.Texto(mensaje) + texto.magnitud = 17 + texto.centro = ("centro", "centro") + texto.izquierda = izquierda + 10 + texto.color = colores.blanco + texto.abajo = abajo + 10 + anterior_texto = texto + +def abrir_cargador(): + """Abre un cargador de ejemplos con varios códigos de prueba. + + Ejemplo: + + >>> pilas.abrir_cargador() + + El cargador de ejemplos se ve de esta forma: + + .. image:: images/cargador.png + """ + try: + import cargador + + cargador.ejecutar() + except ImportError: + print "Lo siento, no tienes instalada la extesion de ejemplos." + print "Instale el paquete 'pilas-examples' para continuar." + return [] diff --git a/pilas/actores/__init__.py b/pilas/actores/__init__.py new file mode 100644 index 0000000..da29e16 --- /dev/null +++ b/pilas/actores/__init__.py @@ -0,0 +1,71 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + + +import math + +import pilas +import utils +from actor import Actor + +todos = [] + +__doc__ = """ +Módulo pilas.actores +==================== + +El módulo actores contiene una serie de clases +para representar personajes de videojuegos. + +Para crear actores en una escena del juego simplemente +se tiene que crear un nuevo objeto a partir de una +clase. + +Por ejemplo, para crear un pongüino podríamos +escribir la siguiente sentencia: + + >>> p = pilas.actores.Pingu() + +""" + + + +from mono import Mono +from ejes import Ejes +from animado import Animado +from animacion import Animacion +from explosion import Explosion +from bomba import Bomba +from pingu import Pingu +from banana import Banana +from texto import Texto +from temporizador import Temporizador +from moneda import Moneda +from pizarra import Pizarra +from pelota import Pelota +from puntaje import Puntaje +from estrella import Estrella +from caja import Caja +from nave import Nave +from disparo import Disparo +from cursordisparo import CursorDisparo +from piedra import Piedra +from menu import Menu +from opcion import Opcion +from tortuga import Tortuga +from mapa import Mapa +from martian import Martian +from boton import Boton +from entradadetexto import EntradaDeTexto +from aceituna import Aceituna +from globo import Globo +from dialogo import Dialogo +from globoelegir import GloboElegir +from pausa import Pausa +from mano import CursorMano +from cooperativista import Cooperativista diff --git a/pilas/actores/aceituna.py b/pilas/actores/aceituna.py new file mode 100644 index 0000000..1e49384 --- /dev/null +++ b/pilas/actores/aceituna.py @@ -0,0 +1,40 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +import pilas +from pilas.actores import Actor + +class Aceituna(Actor): + + def __init__(self, x=0, y=0): + self.cuadro_normal = pilas.imagenes.cargar("aceituna.png") + self.cuadro_reir = pilas.imagenes.cargar("aceituna_risa.png") + self.cuadro_burla = pilas.imagenes.cargar("aceituna_burla.png") + self.cuadro_grita = pilas.imagenes.cargar("aceituna_grita.png") + + Actor.__init__(self, x=x, y=y) + self.imagen = self.cuadro_normal + self.centro = ('centro', 'centro') + self.radio_de_colision = 18 + + def normal(self): + self.imagen = self.cuadro_normal + + def reir(self): + self.imagen = self.cuadro_reir + + def burlarse(self): + self.imagen = self.cuadro_burla + + burlar = burlarse + + def gritar(self): + self.imagen = self.cuadro_grita + + def saltar(self): + self.hacer(pilas.comportamientos.Saltar()) diff --git a/pilas/actores/actor.py b/pilas/actores/actor.py new file mode 100644 index 0000000..9b1cbc2 --- /dev/null +++ b/pilas/actores/actor.py @@ -0,0 +1,409 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +import pilas +from pilas.estudiante import Estudiante + +IZQUIERDA = ["izquierda"] +DERECHA = ["derecha"] +ARRIBA = ["arriba", "superior"] +CENTRO = ["centro", "centrado", "medio", "arriba"] +ABAJO = ["abajo", "inferior", "debajo"] + + +class Actor(object, Estudiante): + """Representa un objeto visible en pantalla, algo que se ve y tiene posicion. + + .. image:: images/actores/actor.png + + Un objeto Actor se tiene que crear siempre indicando una imagen. Si no + se especifica una imagen, se verá una pila de color gris cómo la que + está mas arriba. + + Una forma de crear el actor con una imagen es: + + >>> protagonista = Actor("protagonista_de_frente.png") + + incluso, es equivalente hacer lo siguiente: + + >>> imagen = pilas.imagenes.cargar("protagonista_de_frente.png") + >>> protagonista = Actor(imagen) + + Luego, una vez que ha sido ejecutada la sentencia aparecerá + el nuevo actor para que puedas manipularlo. Por ejemplo + alterando sus propiedades: + + >>> protagonista.x = 100 + >>> protagonista.escala = 2 + >>> protagonista.rotacion = 30 + + Estas propiedades también se pueden manipular mediante + interpolaciones. Por ejemplo, para aumentar el tamaño del + personaje de 1 a 5 en 7 segundos: + + >>> protagonista.escala = 1 + >>> protagonista.escala = [5], 7 + + Si quieres que el actor sea invisible, un truco es crearlo + con la imagen ``invisible.png``: + + >>> invisible = pilas.actores.Actor('invisible.png') + """ + + def __init__(self, imagen="sin_imagen.png", x=0, y=0): + if not pilas.mundo: + mensaje = "Tiene que invocar a la funcion ``pilas.iniciar()`` para comenzar." + print mensaje + raise Exception(mensaje) + + Estudiante.__init__(self) + self._actor = pilas.mundo.motor.obtener_actor(imagen, x=x, y=y) + self.centro = ('centro', 'centro') + + self.x = x + self.y = y + self.transparencia = 0 + + # Define el nivel de lejanía respecto del observador. + self.z = 0 + self._espejado = False + self.radio_de_colision = 10 + pilas.actores.utils.insertar_como_nuevo_actor(self) + self._transparencia = 0 + self.anexados = [] + + def definir_centro(self, (x, y)): + if type(x) == str: + if x not in IZQUIERDA + CENTRO + DERECHA: + raise Exception("No puedes definir '%s' como eje horizontal." %(x)) + x = self._interpretar_y_convertir_posicion(x, self.obtener_ancho()) + if type(y) == str: + if y not in ARRIBA + CENTRO + ABAJO: + raise Exception("No puedes definir '%s' como eje vertical." %(y)) + y = self._interpretar_y_convertir_posicion(y, self.obtener_alto()) + + self._centro = (x, y) + self._actor.definir_centro(x, y) + + def _interpretar_y_convertir_posicion(self, posicion, maximo_valor): + if posicion in IZQUIERDA + ARRIBA: + return 0 + elif posicion in CENTRO: + return int(maximo_valor / 2.0) + elif posicion in DERECHA + ABAJO: + return maximo_valor + else: + raise Exception("El valor '%s' no corresponde a una posicion, use numeros o valores como 'izquierda', 'arriba' etc." %(posicion)) + + def obtener_centro(self): + return self._centro + + centro = property(obtener_centro, definir_centro, doc=""" + Cambia la posición del punto (x, y) dentro de actor. + + Inicialmente, cuando tomamos un actor y definimos sus + atributos (x, y). Ese punto, será el que representa el centro + del personaje. + + Eso hace que las rotaciones sean siempre sobre el centro + del personajes, igual que los cambios de escala y la posición. + + En algunas ocasiones, queremos que el punto (x, y) sea otra + parte del actor. Por ejemplo sus pies. En esos casos + es útil definir el centro del actor. + + Por ejemplo, si queremos mover el centro del actor podemos + usar sentencias cómo estas: + + >>> actor.centro = ("izquierda", "abajo") + >>> actor.centro = ("centro", "arriba") + + Pulsa la tecla **F8** para ver el centro del los actores + dentro de pilas. Es aconsejable pulsar la tecla **+** para + que el punto del modo **F8** se vea bien. + """) + + def definir_posicion(self, x, y): + self._actor.definir_posicion(x, y) + + def obtener_posicion(self): + return self._actor.obtener_posicion() + + def dibujar(self, aplicacion): + self._actor.dibujar(aplicacion) + + def get_x(self): + x, y = self.obtener_posicion() + return x + + @pilas.utils.interpolable + def set_x(self, x): + self.definir_posicion(x, self.y) + + def get_z(self): + return self._z + + @pilas.utils.interpolable + def set_z(self, z): + self._z = z + pilas.actores.utils.ordenar_actores_por_valor_z() + + @pilas.utils.interpolable + def set_y(self, y): + self.definir_posicion(self.x, y) + + def get_y(self): + x, y = self.obtener_posicion() + return y + + @pilas.utils.interpolable + def set_scale(self, s): + if s < 0: + return + + ultima_escala = self.obtener_escala() + + # Se hace la siguiente regla de 3 simple: + # + # ultima_escala self.radio_de_colision + # s ? + + self.definir_escala(s) + self.radio_de_colision = (s * self.radio_de_colision) / max(ultima_escala, 0.0001) + + @pilas.utils.interpolable + def set_scale_x(self, s): + if s < 0: + return + self._actor.definir_escala_x(s) + + @pilas.utils.interpolable + def set_scale_y(self, s): + if s < 0: + return + self._actor.definir_escala_y(s) + + + + def get_scale(self): + return self.obtener_escala() + + def get_scale_x(self): + return self._actor._escala_x + + def get_scale_y(self): + return self._actor._escala_y + + def get_rotation(self): + return self.obtener_rotacion() + + @pilas.utils.interpolable + def set_rotation(self, x): + self.definir_rotacion(x) + + def get_espejado(self): + return self._espejado + + def set_espejado(self, nuevo_valor): + if self._espejado != nuevo_valor: + self._espejado = nuevo_valor + self._actor.set_espejado(nuevo_valor) + + @pilas.utils.interpolable + def set_transparencia(self, nuevo_valor): + self._transparencia = nuevo_valor + self.definir_transparencia(nuevo_valor) + + def get_transparencia(self): + return self._transparencia + + def get_imagen(self): + return self.obtener_imagen() + + def set_imagen(self, imagen): + if isinstance(imagen, str): + imagen = pilas.imagenes.cargar(imagen) + + self.definir_imagen(imagen) + + def get_fijo(self): + return self._actor.fijo + + def set_fijo(self, fijo): + self._actor.fijo = fijo + + + espejado = property(get_espejado, set_espejado, doc="Indica si se tiene que invertir horizonaltamente la imagen del actor.") + z = property(get_z, set_z, doc="Define lejania respecto del observador.") + x = property(get_x, set_x, doc="Define la posición horizontal.") + y = property(get_y, set_y, doc="Define la posición vertical.") + rotacion = property(get_rotation, set_rotation, doc="Angulo de rotación (en grados, de 0 a 360)") + escala = property(get_scale, set_scale, doc="Escala de tamaño, 1 es normal, 2 al doble de tamaño etc...)") + escala_x = property(get_scale_x, set_scale_x, doc="Escala de tamaño horizontal, 1 es normal, 2 al doble de tamaño etc...)") + escala_y = property(get_scale_y, set_scale_y, doc="Escala de tamaño vertical, 1 es normal, 2 al doble de tamaño etc...)") + transparencia = property(get_transparencia, set_transparencia, doc="Define el nivel de transparencia, 0 indica opaco y 100 la maxima transparencia.") + imagen = property(get_imagen, set_imagen, doc="Define la imagen a mostrar.") + fijo = property(get_fijo, set_fijo, doc="Indica si el actor debe ser independiente a la camara.") + + def eliminar(self): + """Elimina el actor de la lista de actores que se imprimen en pantalla.""" + self.destruir() + self._eliminar_anexados() + + def destruir(self): + """Elimina a un actor pero de manera inmediata.""" + pilas.actores.utils.eliminar_un_actor(self) + self.eliminar_habilidades() + self.eliminar_comportamientos() + + def actualizar(self): + """Actualiza el estado del actor. + + Este metodo se llama una vez por frame, y generalmente se suele + usar para implementar el comportamiento del actor. + + Si estás haciendo una subclase de Actor, es aconsejable que re-definas + este método.""" + + def pre_actualizar(self): + """Actualiza comportamiento y habilidades antes de la actualización.""" + self.actualizar_comportamientos() + self.actualizar_habilidades() + + def __cmp__(self, otro_actor): + """Compara dos actores para determinar cual esta mas cerca de la camara. + + Este metodo se utiliza para ordenar los actores antes de imprimirlos + en pantalla. De modo tal que un usuario pueda seleccionar que + actores se ven mas arriba de otros cambiando los valores de + los atributos `z`.""" + + if otro_actor.z >= self.z: + return 1 + else: + return -1 + + def get_izquierda(self): + return self.x - (self.centro[0] * self.escala) + + @pilas.utils.interpolable + def set_izquierda(self, x): + self.x = x + (self.centro[0] * self.escala) + + izquierda = property(get_izquierda, set_izquierda) + + def get_derecha(self): + return self.izquierda + self.obtener_ancho() + + @pilas.utils.interpolable + def set_derecha(self, x): + self.set_izquierda(x - self.ancho) + + derecha = property(get_derecha, set_derecha) + + def get_abajo(self): + return self.get_arriba() - self.alto + + @pilas.utils.interpolable + def set_abajo(self, y): + self.set_arriba(y + self.alto) + + abajo = property(get_abajo, set_abajo) + + def get_arriba(self): + return self.y + (self.centro[1] * self.escala) + + @pilas.utils.interpolable + def set_arriba(self, y): + self.y = y - (self.centro[1] * self.escala) + + arriba = property(get_arriba, set_arriba) + + + def colisiona_con_un_punto(self, x, y): + """Determina si un punto colisiona con el area del actor. + + Todos los actores tienen un area rectangular, pulsa la + tecla **F10** para ver el area de colision. + """ + return self.izquierda <= x <= self.derecha and self.abajo <= y <= self.arriba + + def obtener_rotacion(self): + return self._actor.obtener_rotacion() + + def definir_rotacion(self, r): + r = r % 360 + self._actor.definir_rotacion(r) + + def definir_color(self, c): + self._actor.definir_color(c) + + def obtener_imagen(self): + return self._actor.obtener_imagen() + + def definir_imagen(self, imagen): + self._actor.definir_imagen(imagen) + self.centro = ('centro', 'centro') + + def duplicar(self, **kv): + duplicado = self.__class__() + + for clave in kv: + setattr(duplicado, clave, kv[clave]) + + return duplicado + + def obtener_ancho(self): + return self.imagen.ancho() + + def obtener_alto(self): + return self.imagen.alto() + + ancho = property(obtener_ancho) + alto = property(obtener_alto) + + def __mul__(self, cantidad): + if type(cantidad) is not int or cantidad < 1: + raise TypeError("Solo puede multiplicar por numeros enteros mayores a 1.") + + grupo = pilas.atajos.fabricar(self.__class__, cantidad - 1) + grupo.append(self) + return grupo + + def __str__(self): + return "<%s en (%d, %d)>" %(self.__class__.__name__, self.x, self.y) + + def obtener_escala(self): + return self._actor.obtener_escala() + + def definir_escala(self, escala): + self._actor.definir_escala(escala) + + def definir_transparencia(self, valor): + self._actor.definir_transparencia(valor) + + def imitar(self, otro_actor_o_figura): + self.aprender(pilas.habilidades.Imitar, otro_actor_o_figura) + + def esta_fuera_de_la_pantalla(self): + # TODO: detectar area de la pantalla con las funciones que exporta el motor. + if self.derecha < -320 or self.izquierda > 320 or self.arriba < -240 or self.abajo > 240: + return True + + def decir(self, mensaje, autoeliminar=True): + """Emite un mensaje usando un globo similar al de los commics""" + nuevo_actor = pilas.actores.Globo(mensaje, self.x, self.y, autoeliminar=autoeliminar) + nuevo_actor.z = self.z - 1 + self.anexar(nuevo_actor) + + def anexar(self, otro_actor): + self.anexados.append(otro_actor) + + def _eliminar_anexados(self): + for x in self.anexados: + x.eliminar() diff --git a/pilas/actores/animacion.py b/pilas/actores/animacion.py new file mode 100644 index 0000000..0eb34e4 --- /dev/null +++ b/pilas/actores/animacion.py @@ -0,0 +1,60 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +import pilas +from pilas.actores import Animado +import copy + +VELOCIDAD = 10 + + +class Animacion(Animado): + """Representa una animacion de una grilla de imagenes. + + Este actor toma una grilla de cuadros de animacion + y los reproduce hasta que la animacion termina. Cuando + la animacion termina se elimina a si mismo. + + El constructor tiene algunos parámetros de utilidad: + + - El parámetro ``ciclica`` permite hacer animaciones infinitas, que se repiten siempre, por defecto vale ``False`` que significa que la animación terminará y no se repetirá. + - El parámetro ``velocidad`` tiene que ser un número que indicará la cantidad de cuadros por segundo que se tienen que mostrar en la animación. + + Por ejemplo, para mostrar una explosión infinita podrías + escribir: + + >>> grilla = pilas.imagenes.cargar_grilla("explosion.png", 7) + >>> animacion = pilas.actores.Animacion(grilla, ciclica=True, velocidad=1) + + .. image:: images/actores/explosion.png + """ + + def __init__(self, grilla, ciclica=False, x=0, y=0, velocidad=VELOCIDAD): + Animado.__init__(self, grilla, x=x, y=y) + self.tick = 0 + self.ciclica = ciclica + self.definir_velocidad_de_animacion(velocidad) + + def definir_velocidad_de_animacion(self, velocidad_de_animacion): + self._velocidad_de_animacion = (1000.0 / 60) * velocidad_de_animacion + + def obtener_velocidad_de_animacion(self): + return self._velocidad_de_animacion + + velocidad_de_animacion = property(obtener_velocidad_de_animacion, definir_velocidad_de_animacion, doc="Es la cantidad de cuadros por segundo a mostrar") + + def actualizar(self): + self.tick += self.velocidad_de_animacion + + if self.tick > 1000.0: + self.tick -= 1000.0 + ha_reiniciado = self.imagen.avanzar() + + # Si la animacion ha terminado se elimina de la pantalla. + if ha_reiniciado and not self.ciclica: + self.eliminar() diff --git a/pilas/actores/animado.py b/pilas/actores/animado.py new file mode 100644 index 0000000..024bbae --- /dev/null +++ b/pilas/actores/animado.py @@ -0,0 +1,40 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +import pilas +from pilas.actores import Actor +import copy + +class Animado(Actor): + """Representa un actor que tiene asociada una grilla con cuadros de animacion. + + Una de las variantes que introduce este actor es el + método 'definir_cuadro', que facilita la animación de personajes. + + Por ejemplo, si tenemos una grilla con un pongüino, podríamos + mostrarlo usando este código: + + >>> grilla = pilas.imagenes.cargar_grilla("pingu.png", 10) + >>> actor = Animado(grilla) + >>> actor.definir_cuadro(2) + >>> actor.definir_cuadro(5) + + + .. image:: images/actores/pingu.png + """ + + def __init__(self, grilla, x=0, y=0): + Actor.__init__(self, x=x, y=y) + self.imagen = copy.copy(grilla) + self.definir_cuadro(0) + + def definir_cuadro(self, indice): + "Permite cambiar el cuadro de animación a mostrar" + self.imagen.definir_cuadro(indice) + # FIX: Esta sentencia es muy ambigua, porque no todos actores se deben centrar en ese punto. + self.centro = ('centro', 'centro') diff --git a/pilas/actores/banana.py b/pilas/actores/banana.py new file mode 100644 index 0000000..f283510 --- /dev/null +++ b/pilas/actores/banana.py @@ -0,0 +1,45 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +import pilas +from pilas.actores import Actor + +class Banana(Actor): + """Muestra una banana que se combina (temáticamente) con el actor Mono. + + .. image:: images/actores/banana.png + + + Este actor se podría usar cómo alimento o bonus para otros + actores. + + Este actor tiene solo dos cuadros de animación que se pueden + mostrar con los métodos ``abrir`` y ``cerrar``: + + >>> banana = pilas.actores.Banana() + >>> banana.abrir() + >>> banana.cerrar() + + + """ + + def __init__(self, x=0, y=0): + Actor.__init__(self, x=x, y=y) + self.imagen = pilas.imagenes.cargar_grilla("banana.png", 2) + self.definir_cuadro(0) + + def definir_cuadro(self, indice): + self.imagen.definir_cuadro(indice) + + def abrir(self): + """Muestra el gráfico de la banana abierta con menos cáscara.""" + self.definir_cuadro(1) + + def cerrar(self): + """Muestra el gráfico de banana normal (con cáscara)""" + self.definir_cuadro(0) diff --git a/pilas/actores/bomba.py b/pilas/actores/bomba.py new file mode 100644 index 0000000..71d5a11 --- /dev/null +++ b/pilas/actores/bomba.py @@ -0,0 +1,37 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +import pilas +from pilas.actores import Animacion +from pilas.actores import Explosion + +class Bomba(Animacion): + """Representa una explosion para una bomba, dinamita etc... + + .. image:: images/actores/bomba.png + + La bomba adquiere la habilidad explotar al momento de crearse, así + que puedes invocar a su método "explotar" y la bomba hará un + explosión con sonido en pantalla. + + Este es un ejemplo de uso del actor: + + >>> bomba = pilas.actores.Bomba() + >>> bomba.explotar() + """ + + + def __init__(self, x=0, y=0): + grilla = pilas.imagenes.cargar_grilla("bomba.png", 2) + Animacion.__init__(self, grilla, ciclica=True, x=x, y=y) + self.radio_de_colision = 25 + self.aprender(pilas.habilidades.PuedeExplotar) + + def explotar(self): + "Hace explotar a la bomba y la elimina de la pantalla." + self.eliminar() diff --git a/pilas/actores/boton.py b/pilas/actores/boton.py new file mode 100644 index 0000000..a57abb5 --- /dev/null +++ b/pilas/actores/boton.py @@ -0,0 +1,142 @@ +# -*- encoding: utf-8 -*- +# For Pilas engine - A video game framework. +# +# Copyright 2011 - Pablo Garrido +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar +# + + +from pilas.actores import Actor +import pilas + +class Boton(Actor): + """Representa un boton que reacciona al ser presionado.""" + + def __init__(self, x=0, y=0, + ruta_normal = 'boton/boton_normal.png', + ruta_press = 'boton/boton_press.png', + ruta_over = 'boton/boton_over.png', + ): + + self.ruta_normal = ruta_normal + self.ruta_press = ruta_press + self.ruta_over = ruta_over + + self.funciones_normal = [] + self.funciones_press = [] + self.funciones_over = [] + + self.estado = True + + Actor.__init__(self, ruta_normal, x=x, y=y) + self._cargar_imagenes(self.ruta_normal, self.ruta_press, self.ruta_over) + + pilas.eventos.mueve_mouse.conectar(self.detection_move_mouse) + pilas.eventos.click_de_mouse.conectar(self.detection_click_mouse) + pilas.eventos.termina_click.conectar(self.detection_end_click_mouse) + + def _cargar_imagenes(self, ruta_normal, ruta_press, ruta_over): + self.ruta_normal = ruta_normal + self.ruta_press = ruta_press + self.ruta_over = ruta_over + + self.imagen_over = pilas.imagenes.cargar(ruta_over) + self.imagen_normal = pilas.imagenes.cargar(ruta_normal) + self.imagen_press = pilas.imagenes.cargar(ruta_press) + + + #funciones que conectan evento(press, over, normal) a funciones + def conectar_normal(self, funcion, arg = "null"): + t = (funcion, arg) + self.funciones_normal.append(t) + + def conectar_presionado(self, funcion, arg = "null"): + t = (funcion, arg) + self.funciones_press.append(t) + + def conectar_sobre(self, funcion, arg = "null"): + t = (funcion, arg) + self.funciones_over.append(t) + + def desconectar_normal_todo(self): + self.funciones_normal = [] + + def desconectar_presionado_todo(self): + self.funciones_press = [] + + def desconectar_sobre_todo(self): + self.funciones_over = [] + + def desconectar_normal(self, funcion, arg = "null"): + t = (funcion, arg) + self.funciones_normal.remove(t) + + def desconectar_presionado(self, funcion, arg = "null"): + t = (funcion, arg) + self.funciones_press.remove(t) + + def desconectar_sobre(self, funcion, arg = "null"): + t = (funcion, arg) + self.funciones_over.remove(t) + + def ejecutar_funciones_normal(self): + if self.estado == True: + for i in self.funciones_normal: + if i[1] == "null": + i[0]() + else: + i[0](i[1]) + + def ejecutar_funciones_press(self): + if self.estado == True: + for i in self.funciones_press: + if i[1] == "null": + i[0]() + else: + i[0](i[1]) + + + def ejecutar_funciones_over(self): + if self.estado == True: + for i in self.funciones_over: + if i[1] == "null": + i[0]() + else: + i[0](i[1]) + + # funciones para inactivar o activar las funciones conectadas + def activar(self): + self.estado = True + + def desactivar(self): + self.estado = False + + # funciones que cambian la imagen del boton + def pintar_normal(self): + self.definir_imagen(self.imagen_normal) + + def pintar_presionado(self, ruta_press = "null"): + if ruta_press == "null": + self.imagen_press = pilas.imagenes.cargar(self.ruta_press) + else: + self.imagen_press = pilas.imagenes.cargar(ruta_press) + + self.definir_imagen(self.imagen_press) + + def pintar_sobre(self): + self.definir_imagen(self.imagen_over) + + def detection_move_mouse(self, evento): + if self.colisiona_con_un_punto(evento.x, evento.y): + self.ejecutar_funciones_over() + else: + self.ejecutar_funciones_normal() + + def detection_click_mouse(self, click): + if self.colisiona_con_un_punto(click.x, click.y): + self.ejecutar_funciones_press() + + def detection_end_click_mouse(self, end_click): + pass diff --git a/pilas/actores/caja.py b/pilas/actores/caja.py new file mode 100644 index 0000000..c038ea6 --- /dev/null +++ b/pilas/actores/caja.py @@ -0,0 +1,23 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +from pilas.actores import Actor +import pilas + +class Caja(Actor): + "Representa un bloque que tiene fisica como una caja." + + def __init__(self, x=0, y=0): + imagen = pilas.imagenes.cargar('caja.png') + Actor.__init__(self, imagen) + self.rotacion = 0 + self.x = x + self.y = y + self.radio_de_colision = 25 + + self.aprender(pilas.habilidades.RebotarComoCaja) diff --git a/pilas/actores/cooperativista.py b/pilas/actores/cooperativista.py new file mode 100644 index 0000000..5f00421 --- /dev/null +++ b/pilas/actores/cooperativista.py @@ -0,0 +1,105 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +import pilas +from pilas.actores import Actor +from pilas.comportamientos import Comportamiento + +VELOCIDAD = 4 + +class Cooperativista(Actor): + + def __init__(self, x=0, y=0): + Actor.__init__(self, x=x, y=y) + self._cargar_animaciones() + self.hacer(Esperando()) + self.radio_de_colision = 30 + + def _cargar_animaciones(self): + cargar = pilas.imagenes.cargar_grilla + self.animaciones = { + "ok": cargar("cooperativista/ok.png", 1), + "parado": cargar("cooperativista/parado.png", 1), + "camina": cargar("cooperativista/camina.png", 4), + # las siguientes estan sin usar... + "alerta": cargar("cooperativista/alerta.png", 2), + "trabajando": cargar("cooperativista/trabajando.png", 1), + "parado_sujeta": cargar("cooperativista/parado_sujeta.png", 1), + "camina_sujeta": cargar("cooperativista/camina_sujeta.png", 4), + } + + def definir_cuadro(self, indice): + self.imagen.definir_cuadro(indice) + + def cambiar_animacion(self, nombre): + self.imagen = self.animaciones[nombre] + self.centro = ("centro", "abajo") + +class Esperando(Comportamiento): + "Un actor en posicion normal o esperando a que el usuario pulse alguna tecla." + + def iniciar(self, receptor): + self.receptor = receptor + self.receptor.cambiar_animacion("parado") + self.receptor.definir_cuadro(0) + + def actualizar(self): + if pilas.mundo.control.izquierda: + self.receptor.hacer(Caminando()) + elif pilas.mundo.control.derecha: + self.receptor.hacer(Caminando()) + + if pilas.mundo.control.arriba: + self.receptor.hacer(DecirOk()) + + +class Caminando(Comportamiento): + + def __init__(self): + self.cuadros = [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3] + self.paso = 0 + + def iniciar(self, receptor): + self.receptor = receptor + self.receptor.cambiar_animacion("camina") + + def actualizar(self): + self.avanzar_animacion() + + if pilas.mundo.control.izquierda: + self.receptor.x -= VELOCIDAD + self.receptor.espejado = False + elif pilas.mundo.control.derecha: + self.receptor.x += VELOCIDAD + self.receptor.espejado = True + else: + self.receptor.hacer(Esperando()) + + def avanzar_animacion(self): + self.paso += 1 + + if self.paso >= len(self.cuadros): + self.paso = 0 + + self.receptor.definir_cuadro(self.cuadros[self.paso]) + + +class DecirOk(Comportamiento): + + def __init__(self): + self.paso = 0 + + def iniciar(self, receptor): + self.receptor = receptor + self.receptor.cambiar_animacion("ok") + + def actualizar(self): + self.paso += 1 + + if self.paso > 50: + self.receptor.hacer(Esperando()) diff --git a/pilas/actores/cursordisparo.py b/pilas/actores/cursordisparo.py new file mode 100644 index 0000000..96c9cca --- /dev/null +++ b/pilas/actores/cursordisparo.py @@ -0,0 +1,24 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +from pilas.actores import Actor +import pilas + +class CursorDisparo(Actor): + "Representa un bloque que tiene fisica como una caja." + + def __init__(self, x=0, y=0): + imagen = pilas.imagenes.cargar('cursordisparo.png') + Actor.__init__(self, imagen) + self.rotacion = 0 + self.x = x + self.y = y + self.radio_de_colision = 25 + + self.aprender(pilas.habilidades.SeguirAlMouse) + pilas.mundo.motor.ocultar_puntero_del_mouse() diff --git a/pilas/actores/dialogo.py b/pilas/actores/dialogo.py new file mode 100644 index 0000000..d6089ad --- /dev/null +++ b/pilas/actores/dialogo.py @@ -0,0 +1,72 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +import pilas + +class Dialogo: + "Representa una secuencia de mensajes entre varios actores." + + def __init__(self, modo_automatico=True): + self.dialogo = [] + self.dialogo_actual = None + self.modo_automatico = modo_automatico + + def decir(self, actor, texto): + self.dialogo.append((actor, texto)) + + def decir_inmediatamente(self, actor, texto): + self.dialogo = [] + self._eliminar_dialogo_actual() + self.decir(actor, texto) + siguiente = self.obtener_siguiente_dialogo_o_funcion() + self._mostrar_o_ejecutar_siguiente(siguiente) + + def elegir(self, actor, texto, opciones, funcion_a_invocar): + self.dialogo.append((actor, texto, opciones, funcion_a_invocar)) + + def ejecutar(self, funcion): + self.dialogo.append(funcion) + + def iniciar(self): + self.avanzar_al_siguiente_dialogo() + + def obtener_siguiente_dialogo_o_funcion(self): + if self.dialogo: + return self.dialogo.pop(0) + + def _eliminar_dialogo_actual(self): + if self.dialogo_actual: + self.dialogo_actual.eliminar() + self.dialogo_actual = None + + def _mostrar_o_ejecutar_siguiente(self, siguiente): + if isinstance(siguiente, tuple): + # Es un mensaje de dialogo simple + if len(siguiente) == 2: + actor, texto = siguiente + self.dialogo_actual = pilas.actores.Globo(texto, dialogo=self, avance_con_clicks=self.modo_automatico) + else: + # Es un mensaje con seleccion. + actor, texto, opciones, funcion_a_invocar = siguiente + self.dialogo_actual = pilas.actores.GloboElegir(texto, opciones, funcion_a_invocar, dialogo=self) + + self.dialogo_actual.colocar_origen_del_globo(actor.x, actor.arriba) + else: + siguiente() + self.avanzar_al_siguiente_dialogo() + + def avanzar_al_siguiente_dialogo(self, evento=None): + self._eliminar_dialogo_actual() + siguiente = self.obtener_siguiente_dialogo_o_funcion() + + if siguiente: + self._mostrar_o_ejecutar_siguiente(siguiente) + else: + return False + + return True diff --git a/pilas/actores/disparo.py b/pilas/actores/disparo.py new file mode 100644 index 0000000..ccd19c0 --- /dev/null +++ b/pilas/actores/disparo.py @@ -0,0 +1,33 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +import pilas +from pilas.actores import Animacion +import math + +class Disparo(Animacion): + "Representa un disparo que avanza." + + def __init__(self, x=0, y=0, rotacion=0, velocidad=2): + self.velocidad = velocidad + grilla = pilas.imagenes.cargar_grilla("disparo.png", 2) + Animacion.__init__(self, grilla, ciclica=True, x=x, y=y) + self.radio_de_colision = 10 + self.rotacion = rotacion + + def actualizar(self): + Animacion.actualizar(self) + self.avanzar() + + def avanzar(self): + "Hace avanzar la nave en direccion a su angulo." + rotacion_en_radianes = math.radians(-self.rotacion + 90) + dx = math.cos(rotacion_en_radianes) * self.velocidad + dy = math.sin(rotacion_en_radianes) * self.velocidad + self.x += dx + self.y += dy diff --git a/pilas/actores/ejes.py b/pilas/actores/ejes.py new file mode 100644 index 0000000..c62ed52 --- /dev/null +++ b/pilas/actores/ejes.py @@ -0,0 +1,26 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +from pilas.actores import Actor + +class Ejes(Actor): + """Representa el eje de coordenadas tomado como sistema de referencia. + + Este actor es útil para mostrar que la ventana + de pilas tiene una referencia, y que las posiciones + responden a este modelo. + + Para crear el eje podrías ejecutar: + + >>> eje = pilas.actore.Eje() + + """ + + def __init__(self, x=0, y=0): + Actor.__init__(self, "ejes.png", x=x, y=y) + self.z = 100 diff --git a/pilas/actores/entradadetexto.py b/pilas/actores/entradadetexto.py new file mode 100644 index 0000000..8902b08 --- /dev/null +++ b/pilas/actores/entradadetexto.py @@ -0,0 +1,53 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +from pilas.actores import Actor +import pilas + +class EntradaDeTexto(Actor): + """Representa una caja de texto que puede servir para ingresar texto. + + Este actor, en la mayoria de los casos, se utiliza para solicitarle + el nombre a un usuario. Por ejemplo, cuando completa un record + de puntaje.""" + + def __init__(self, x=0, y=0, color=pilas.colores.negro, limite=10, tamano=32, fuente='Arial', cursor_intermitente=True): + self.cursor = "|" + self.texto = "" + self.limite = limite + imagen = pilas.imagenes.cargar_superficie(640, 480) + Actor.__init__(self, imagen) + pilas.eventos.pulsa_tecla.conectar(self.cuando_pulsa_una_tecla) + self._actualizar_imagen() + + if cursor_intermitente: + pilas.mundo.agregar_tarea_siempre(0.25, self._actualizar_cursor) + + def _actualizar_cursor(self): + if self.cursor == "": + self.cursor = "|" + else: + self.cursor = "" + + self._actualizar_imagen() + return True + + + def cuando_pulsa_una_tecla(self, evento): + if evento.codigo == '\x08': + # Indica que se quiere borrar un caracter + self.texto = self.texto[:-1] + else: + if len(self.texto) < self.limite: + self.texto = self.texto + evento.texto + + self._actualizar_imagen() + + def _actualizar_imagen(self): + self.imagen.pintar(pilas.colores.blanco) + self.imagen.texto(self.texto + self.cursor, 100, 100) diff --git a/pilas/actores/estrella.py b/pilas/actores/estrella.py new file mode 100644 index 0000000..019b57e --- /dev/null +++ b/pilas/actores/estrella.py @@ -0,0 +1,19 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +from pilas.actores import Actor +import pilas + + +class Estrella(Actor): + "Representa una estrella de color amarillo." + + def __init__(self, x=0, y=0): + imagen = pilas.imagenes.cargar('estrella.png') + Actor.__init__(self, imagen, x=x, y=y) + self.rotacion = 0 diff --git a/pilas/actores/explosion.py b/pilas/actores/explosion.py new file mode 100644 index 0000000..930d7b3 --- /dev/null +++ b/pilas/actores/explosion.py @@ -0,0 +1,39 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +import pilas +from pilas.actores import Animacion + +class Explosion(Animacion): + """Representa una explosion para una bomba, dinamita etc... + + El actor simplemente aparece reproduciendo un sonido y + haciendo una animación: + + >>> actor = pilas.actores.Bomba() + + .. image:: images/actores/explosion.png + + y una vez que termina se elimina a sí mismo. + + Este actor también se puede anexar a cualquier + otro para producir explosiones. Cuando enseñamos a un + actor a explotar (por ejemplo un pingüino), el actor + ``Explosion`` aparece cuando se elimina al actor:: + + >>> actor = pilas.actores.Pingu() + >>> actor.aprender(pilas.habilidades.PuedeExplotar) + >>> actor.eliminar() + """ + + + def __init__(self, x=0, y=0): + grilla = pilas.imagenes.cargar_grilla("explosion.png", 7) + Animacion.__init__(self, grilla, x=x, y=y) + self.sonido_explosion = pilas.sonidos.cargar("explosion.wav") + self.sonido_explosion.reproducir() diff --git a/pilas/actores/globo.py b/pilas/actores/globo.py new file mode 100644 index 0000000..744798e --- /dev/null +++ b/pilas/actores/globo.py @@ -0,0 +1,79 @@ +# -*- encoding: utf-8 -*- +import pilas +from pilas.actores import Actor + +class Globo(Actor): + "Representa un cuadro de dialogo estilo historietas." + + def __init__(self, texto, x=0, y=0, dialogo=None, avance_con_clicks=True, autoeliminar=False): + self.dialogo = dialogo + Actor.__init__(self, imagen='invisible.png', x=x, y=y) + + ancho, alto = pilas.utils.obtener_area_de_texto(texto) + + ancho = int((ancho + 12) - (ancho % 12)) + alto = int((alto + 12) - alto % 12) + + self.imagen = pilas.imagenes.cargar_superficie(ancho + 36, alto + 24 + 35) + + self._pintar_globo(ancho, alto) + self.imagen.texto(texto, 17, 30) + self.centro = ("derecha", "abajo") + self.escala = 0.1 + self.escala = [1], 0.2 + + if avance_con_clicks: + pilas.eventos.click_de_mouse.conectar(self.cuando_quieren_avanzar) + + if autoeliminar: + pilas.mundo.tareas.una_vez(3, self.eliminar) + + def colocar_origen_del_globo(self, x, y): + "Cambia la posicion del globo para que el punto de donde se emite el globo sea (x, y)." + self.x = x + self.y = y + + + def cuando_quieren_avanzar(self, *k): + if self.dialogo: + self.dialogo.avanzar_al_siguiente_dialogo() + else: + self.eliminar() + + def _pintar_globo(self, ancho, alto): + imagen = pilas.imagenes.cargar("globo.png") + + # esquina sup-izq + self.imagen.pintar_parte_de_imagen(imagen, 0, 0, 12, 12, 0, 0) + + # borde superior + for x in range(0, int(ancho) + 12, 12): + self.imagen.pintar_parte_de_imagen(imagen, 12, 0, 12, 12, 12 + x, 0) + + # esquina sup-der + self.imagen.pintar_parte_de_imagen(imagen, 100, 0, 12, 12, 12 + int(ancho) + 12, 0) + + # centro del dialogo + for y in range(0, int(alto) + 12, 12): + # borde izquierdo + self.imagen.pintar_parte_de_imagen(imagen, 0, 12, 12, 12, 0, 12 + y) + # linea horizontal blanca, para el centro del dialogo. + for x in range(0, int(ancho) + 12, 12): + self.imagen.pintar_parte_de_imagen(imagen, 12, 12, 12, 12, 12 + x, 12 + y) + + # borde derecho + self.imagen.pintar_parte_de_imagen(imagen, 100, 12, 12, 12, 12 + int(ancho) + 12, 12 + y) + + # parte inferior + self.imagen.pintar_parte_de_imagen(imagen, 0, 35, 12, 12, 0, 0 + int(alto) + 12 + 12) + + # linea horizontal de la parte inferior + for x in range(0, int(ancho) + 12, 12): + self.imagen.pintar_parte_de_imagen(imagen, 12, 35, 12, 12, 12 + x, 0 + int(alto) + 12 + 12) + + self.imagen.pintar_parte_de_imagen(imagen, 100, 35, 12, 12, 12 + int(ancho) + 12, 0 + int(alto) + 12 + 12) + # Pico de la parte de abajo + self.imagen.pintar_parte_de_imagen(imagen, 67, 35, 33, 25, int(ancho) - 12, 0 + int(alto) + 12 + 12) + + def eliminar(self): + Actor.eliminar(self) diff --git a/pilas/actores/globoelegir.py b/pilas/actores/globoelegir.py new file mode 100644 index 0000000..0340dfc --- /dev/null +++ b/pilas/actores/globoelegir.py @@ -0,0 +1,37 @@ +# -*- encoding: utf-8 -*- +import pilas +from pilas.actores.globo import Globo + +class GloboElegir(Globo): + + def __init__(self, texto, opciones, funcion_a_invocar, x=0, y=0, dialogo=None): + self.dialogo = dialogo + self.opciones = opciones + self.funcion_a_invocar = funcion_a_invocar + espacio = "\n" * (len(opciones) +1) # representa un espacio en blanco para poner la seleccion + Globo.__init__(self, texto + espacio, x, y, dialogo=dialogo) + + self.lista_seleccion = pilas.interfaz.ListaSeleccion(opciones, self._cuando_selecciona_opcion, x, y) + self.lista_seleccion.escala = 0.1 + self.lista_seleccion.escala = [1], 0.2 + + def colocar_origen_del_globo(self, x, y): + self.lista_seleccion.centro = ("derecha", "abajo") + self.lista_seleccion.x = x - 10 + self.lista_seleccion.y = y - 10 + + def _obtener_area_para_el_texto(self, texto): + ancho, alto = self.lienzo.obtener_area_de_texto(texto, tamano=14) + opciones_ancho, opciones_alto = self.lienzo.obtener_area_para_lista_de_texto(self.opciones, tamano=14) + return ancho + opciones_ancho, alto + opciones_alto + + def _escribir_texto(self, texto): + self.lienzo.escribir(texto, 12, 25, tamano=14) + + def cuando_quieren_avanzar(self, *k): + pass + + def _cuando_selecciona_opcion(self, opcion): + self.funcion_a_invocar(opcion) + Globo.cuando_quieren_avanzar(self) + self.lista_seleccion.eliminar() diff --git a/pilas/actores/mano.py b/pilas/actores/mano.py new file mode 100644 index 0000000..4d79e5f --- /dev/null +++ b/pilas/actores/mano.py @@ -0,0 +1,46 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +from pilas.actores import Actor +import pilas + +class CursorMano(Actor): + "Representa un bloque que tiene fisica como una caja." + + def __init__(self, x=0, y=0): + self._cargar_imagenes() + Actor.__init__(self, self.imagen_normal) + self.x = x + self.y = y + + self.aprender(pilas.habilidades.SeguirAlMouse) + pilas.mundo.motor.ocultar_puntero_del_mouse() + self.z = -200 + self.pulsado = False + + self.centro = ("izquierda", "arriba") + + pilas.eventos.mueve_mouse.conectar(self.cuando_mueve_el_mouse) + pilas.eventos.click_de_mouse.conectar(self.cuando_pulsa_el_mouse) + pilas.eventos.termina_click.conectar(self.cuando_suelta_el_mouse) + + def _cargar_imagenes(self): + self.imagen_normal = pilas.imagenes.cargar("cursores/normal.png") + self.imagen_arrastrando = pilas.imagenes.cargar("cursores/arrastrando.png") + + def cuando_pulsa_el_mouse(self, evento): + self.pulsado = True + + def cuando_mueve_el_mouse(self, evento): + if self.pulsado: + self.imagen = self.imagen_arrastrando + + def cuando_suelta_el_mouse(self, evento): + if self.pulsado: + self.imagen = self.imagen_normal + self.pulsado = False diff --git a/pilas/actores/mapa.py b/pilas/actores/mapa.py new file mode 100644 index 0000000..aedec79 --- /dev/null +++ b/pilas/actores/mapa.py @@ -0,0 +1,130 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +import pilas +from pilas.actores import Actor + + +class Mapa(Actor): + """Representa mapas creados a partir de imagenes mas pequeñas. + + Este actor te permite crear escenarios tipo ``tiles``, una técnica + de contrucción de escenarios muy popular en los videojuegos. + + Puedes crear un actor a partir de una grilla, e indicando cada + uno los bloques o simplemente usando un programa externo llamado + **tiled** (ver http://www.mapeditor.org). + + Por ejemplo, para crear un mapa desde un archivo del programa + **tiled** puedes escribir: + + >>> mapa = pilas.actores.Mapa('untitled2.tmx') + """ + + def __init__(self, grilla_o_mapa=None, x=0, y=0, restitucion=0.56): + Actor.__init__(self, 'invisible.png', x, y) + self.restitucion = restitucion + self.figuras = [] + self.bloques = [] + + if not grilla_o_mapa: + grilla_o_mapa = grilla = pilas.imagenes.cargar_grilla("grillas/plataformas_10_10.png", 10, 10) + + self.grilla_o_mapa = grilla_o_mapa + + if isinstance(grilla_o_mapa, str): + self._cargar_mapa(grilla_o_mapa) + else: + self.grilla = grilla_o_mapa + self._ancho_cuadro = grilla_o_mapa.cuadro_ancho + self._alto_cuadro = grilla_o_mapa.cuadro_alto + + def _cargar_mapa(self, archivo): + "Carga el escenario desde un archivo .tmz (del programa tiled)." + + archivo = pilas.utils.obtener_ruta_al_recurso(archivo) + + # Carga los nodos principales. + nodo = pilas.utils.xmlreader.makeRootNode(archivo) + nodo_mapa = nodo.getChild('map') + nodo_tileset = nodo_mapa.getChild('tileset') + + # Cantidad de bloques en el mapa. + self.columnas = int(nodo_mapa.getAttributeValue('width')) + self.filas = int(nodo_mapa.getAttributeValue('height')) + + # Atributos de la imagen asociada al mapa. + self._ruta = nodo_tileset.getChild('image').getAttributeValue('source') + self._ruta = pilas.utils.obtener_ruta_al_recurso(self._ruta) + + self._ancho_imagen = int(nodo_tileset.getChild('image').getAttributeValue('width')) + self._alto_imagen = int(nodo_tileset.getChild('image').getAttributeValue('height')) + self._ancho_cuadro = int(nodo_tileset.getAttributeValue('tilewidth')) + self._alto_cuadro = int(nodo_tileset.getAttributeValue('tileheight')) + + # Carga la grilla de imagenes desde el mapa. + self.grilla = pilas.imagenes.cargar_grilla(self._ruta, + self._ancho_imagen / self._ancho_cuadro, + self._alto_imagen / self._alto_cuadro) + + # Carga las capas del mapa. + layers = nodo.getChild('map').getChildren('layer') + + if len(layers) == 0: + raise Exception("Debe tener al menos una capa (layer).") + + # La capa 0 (inferior) define los bloques no-solidos. + self._crear_bloques(layers[0], solidos=False) + + # El resto de las capas definen bloques solidos + for layer in layers[1:]: + self._crear_bloques(layer, solidos=True) + + def _crear_bloques(self, capa, solidos): + "Genera actores que representan los bloques del escenario." + datos = capa.getChild('data').getData() + + # Convierte todo el mapa en una matriz de numeros. + bloques = [[int(x) for x in x.split(',') if x] for x in datos.split()] + + for (y, fila) in enumerate(bloques): + for (x, bloque) in enumerate(fila): + if bloque: + self.pintar_bloque(y, x, bloque -1, solidos) + + def pintar_bloque(self, fila, columna, indice, es_bloque_solido=False): + nuevo_bloque = pilas.actores.Actor('invisible.png') + nuevo_bloque.imagen = self.grilla + nuevo_bloque.imagen.definir_cuadro(indice) + nuevo_bloque.izquierda = columna * self._ancho_cuadro - 320 + nuevo_bloque.arriba = -fila * self._alto_cuadro + 240 + self.bloques.append(nuevo_bloque) + + if es_bloque_solido: + figura = pilas.fisica.Rectangulo(nuevo_bloque.izquierda + self._ancho_cuadro / 2, + nuevo_bloque.arriba - self._alto_cuadro / 2, + self._ancho_cuadro, self._alto_cuadro, dinamica=False, + restitucion=self.restitucion) + self.figuras.append(figura) + + + def reiniciar(self): + self._eliminar_bloques() + + if isinstance(self.grilla_o_mapa, str): + self._cargar_mapa(self.grilla_o_mapa) + + def eliminar(self): + self._eliminar_bloques() + + def _eliminar_bloques(self): + for b in self.bloques: + b.eliminar() + + for f in self.figuras: + f.eliminar() diff --git a/pilas/actores/martian.py b/pilas/actores/martian.py new file mode 100644 index 0000000..00076ef --- /dev/null +++ b/pilas/actores/martian.py @@ -0,0 +1,151 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +import pilas +from pilas.actores import Actor +from pilas.comportamientos import Comportamiento + +VELOCIDAD = 100 + + +class Martian(Actor): + + def __init__(self, x=0, y=0): + Actor.__init__(self, x=x, y=y) + self.imagen = pilas.imagenes.cargar_grilla("marcianitos/martian.png", 12) + self.definir_cuadro(0) + self.figura = pilas.fisica.Circulo(0, 0, 18, friccion=0, amortiguacion=0, restitucion=0.1) + self.hacer(Esperando()) + + def definir_cuadro(self, indice): + self.imagen.definir_cuadro(indice) + self.definir_centro((32, 123)) + + def actualizar(self): + "Sigue el movimiento de la figura." + self.x = self.figura.x + self.y = self.figura.y - 15 + self.figura.rotacion = 0 + + def crear_disparo(self): + if self.espejado: + rotacion = -90 + else: + rotacion = 90 + + disparo = pilas.actores.Disparo(x=self.x, y=self.y+20, rotacion=rotacion, velocidad=10) + + def puede_saltar(self): + dx, dy = self.figura.obtener_velocidad_lineal() + return -2 < dy < 2 + +class Esperando(Comportamiento): + "Un actor en posicion normal o esperando a que el usuario pulse alguna tecla." + + def iniciar(self, receptor): + self.receptor = receptor + self.receptor.definir_cuadro(0) + + def actualizar(self): + + if pilas.mundo.control.izquierda: + self.receptor.hacer(Caminando()) + elif pilas.mundo.control.derecha: + self.receptor.hacer(Caminando()) + + if pilas.mundo.control.arriba and self.receptor.puede_saltar(): + self.receptor.hacer(Saltando()) + + if pilas.mundo.control.boton: + self.receptor.hacer(Disparar(self.receptor)) + +class Caminando(Comportamiento): + + def __init__(self): + self.cuadros = [1, 1, 1, 2, 2, 2] + self.paso = 0 + + def iniciar(self, receptor): + self.receptor = receptor + + def actualizar(self): + self.avanzar_animacion() + + if pilas.mundo.control.izquierda: + self.receptor.figura.definir_velocidad_lineal(-VELOCIDAD) + self.receptor.espejado = True + elif pilas.mundo.control.derecha: + self.receptor.figura.definir_velocidad_lineal(+VELOCIDAD) + self.receptor.espejado = False + else: + self.receptor.figura.definir_velocidad_lineal(0) + self.receptor.hacer(Esperando()) + + if pilas.mundo.control.arriba: + self.receptor.hacer(Saltando()) + + def avanzar_animacion(self): + self.paso += 1 + + if self.paso >= len(self.cuadros): + self.paso = 0 + + self.receptor.definir_cuadro(self.cuadros[self.paso]) + +class Saltando(Comportamiento): + + def iniciar(self, receptor): + self.receptor = receptor + self.receptor.definir_cuadro(3) + self.esta_bajando = False + self.receptor.figura.definir_velocidad_lineal(None, 300) + + + def actualizar(self): + + # obtiene la velocidad del personaje para detectar cuando + # toca el suelo. + vx, vy = self.receptor.figura.obtener_velocidad_lineal() + + if vy < 0: + self.esta_bajando = True + + if self.esta_bajando and -2 < vy < 2: + self.receptor.figura.definir_velocidad_lineal(0,0) + self.receptor.hacer(Esperando()) + + if pilas.mundo.control.izquierda: + self.receptor.espejado = True + self.receptor.figura.definir_velocidad_lineal(-VELOCIDAD) + elif pilas.mundo.control.derecha: + self.receptor.espejado = False + self.receptor.figura.definir_velocidad_lineal(VELOCIDAD) + else: + self.receptor.figura.definir_velocidad_lineal(0) + +class Disparar(Comportamiento): + + def __init__(self, receptor): + self.cuadros = [6, 6, 7, 7, 8, 8] + self.paso = 0 + receptor.crear_disparo() + + def actualizar(self): + termina = self.avanzar_animacion() + + if termina: + self.receptor.hacer(Esperando()) + + def avanzar_animacion(self): + self.paso += 1 + + if self.paso >= len(self.cuadros): + self.paso = 0 + return True + + self.receptor.definir_cuadro(self.cuadros[self.paso]) diff --git a/pilas/actores/menu.py b/pilas/actores/menu.py new file mode 100644 index 0000000..d488118 --- /dev/null +++ b/pilas/actores/menu.py @@ -0,0 +1,115 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +from pilas.actores import Actor +from pilas.grupo import Grupo +import pilas + +DEMORA = 14 + +class Menu(Actor): + "Representa un bloque que tiene fisica como una caja." + + def __init__(self, opciones, x=0, y=0): + self.opciones_como_actores = [] + self.demora_al_responder = 0 + Actor.__init__(self, "invisible.png", x=x, y=y) + self._verificar_opciones(opciones) + self.crear_texto_de_las_opciones(opciones) + self.opciones = opciones + self.seleccionar_primer_opcion() + self.opcion_actual = 0 + # contador para evitar la repeticion de teclas + self.activar() + + def activar(self): + pilas.eventos.mueve_mouse.conectar(self.cuando_mueve_el_mouse) + pilas.eventos.click_de_mouse.conectar(self.cuando_hace_click_con_el_mouse) + + def desactivar(self): + pilas.eventos.mueve_mouse.desconectar(self.cuando_mueve_el_mouse) + pilas.eventos.click_de_mouse.desconectar(self.cuando_hace_click_con_el_mouse) + + def crear_texto_de_las_opciones(self, opciones): + "Genera un actor por cada opcion del menu." + + for indice, (texto, funcion) in enumerate(opciones): + y = self.y - indice * 50 + opciones = pilas.actores.Opcion(texto, x=0, y=y, funcion_a_invocar=funcion) + + self.opciones_como_actores.append(opciones) + + def seleccionar_primer_opcion(self): + if self.opciones_como_actores: + self.opciones_como_actores[0].resaltar() + + def _verificar_opciones(self, opciones): + "Se asegura de que la lista este bien definida." + + for x in opciones: + if not isinstance(x, tuple) or len(x) != 2: + raise Exception("Opciones incorrectas, cada opcion tiene que ser una tupla.") + + def actualizar(self): + "Se ejecuta de manera periodica." + + if self.demora_al_responder < 0: + if pilas.mundo.control.boton: + self.seleccionar_opcion_actual() + self.demora_al_responder = DEMORA + + if pilas.mundo.control.abajo: + self.mover_cursor(1) + self.demora_al_responder = DEMORA + elif pilas.mundo.control.arriba: + self.mover_cursor(-1) + self.demora_al_responder = DEMORA + + self.demora_al_responder -= 1 + + def seleccionar_opcion_actual(self): + opcion = self.opciones_como_actores[self.opcion_actual] + opcion.seleccionar() + + def mover_cursor(self, delta): + # Deja como no-seleccionada la opcion actual. + self._deshabilitar_opcion_actual() + + # Se asegura que las opciones esten entre 0 y 'cantidad de opciones'. + self.opcion_actual += delta + self.opcion_actual %= len(self.opciones_como_actores) + + # Selecciona la opcion nueva. + self.opciones_como_actores[self.opcion_actual].resaltar() + + def __setattr__(self, atributo, valor): + # Intenta propagar la accion a los actores del grupo. + try: + for x in self.opciones_como_actores: + setattr(x, atributo, valor) + except AttributeError: + pass + + Actor.__setattr__(self, atributo, valor) + + def cuando_mueve_el_mouse(self, evento): + "Permite cambiar la opcion actual moviendo el mouse. Retorna True si el mouse esta sobre alguna opcion." + for indice, opcion in enumerate(self.opciones_como_actores): + if opcion.colisiona_con_un_punto(evento.x, evento.y): + if indice != self.opcion_actual: + self._deshabilitar_opcion_actual() + self.opcion_actual = indice + self.opciones_como_actores[indice].resaltar() + return True + + def _deshabilitar_opcion_actual(self): + self.opciones_como_actores[self.opcion_actual].resaltar(False) + + def cuando_hace_click_con_el_mouse(self, evento): + if self.cuando_mueve_el_mouse(evento): + self.seleccionar_opcion_actual() diff --git a/pilas/actores/moneda.py b/pilas/actores/moneda.py new file mode 100644 index 0000000..b4079b0 --- /dev/null +++ b/pilas/actores/moneda.py @@ -0,0 +1,22 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +import pilas +from pilas.actores import Animacion + +class Moneda(Animacion): + """Representa una moneda con animación. + + >>> moneda = pilas.actores.Moneda() + + .. image:: images/actores/moneda.png + + """ + + def __init__(self, x=0, y=0): + Animacion.__init__(self, pilas.imagenes.cargar_grilla("moneda.png", 8), ciclica=True, x=x, y=y) diff --git a/pilas/actores/mono.py b/pilas/actores/mono.py new file mode 100644 index 0000000..012ab74 --- /dev/null +++ b/pilas/actores/mono.py @@ -0,0 +1,69 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +from pilas.actores import Actor +import pilas + +class Mono(Actor): + """Representa la cara de un mono de color marrón. + + .. image:: images/actores/mono.png + + Este personaje se usa como ejemplo básico de un actor. Por + ejemplo, esta es una forma de usar al actor: + + >>> mono = pilas.actores.Mono() + >>> mono.decir("Hola!!!") + >>> mono.gritar() + """ + + def __init__(self, x=0, y=0): + # carga las imagenes adicionales. + self.image_normal = pilas.imagenes.cargar('monkey_normal.png') + self.image_smile = pilas.imagenes.cargar('monkey_smile.png') + self.image_shout = pilas.imagenes.cargar('monkey_shout.png') + + self.sound_shout = pilas.sonidos.cargar('shout.wav') + self.sound_smile = pilas.sonidos.cargar('smile.wav') + + # Inicializa el actor. + Actor.__init__(self, self.image_normal, x=x, y=y) + self.radio_de_colision = 50 + + def sonreir(self): + """Hace que el mono sonria y emita un sonido.""" + self.definir_imagen(self.image_smile) + # Luego de un segundo regresa a la normalidad + pilas.mundo.agregar_tarea_una_vez(0.5, self.normal) + self.sound_smile.reproducir() + + def gritar(self): + """Hace que el mono grite emitiendo un sonido.""" + self.definir_imagen(self.image_shout) + # Luego de un segundo regresa a la normalidad + pilas.mundo.agregar_tarea_una_vez(1, self.normal) + self.sound_shout.reproducir() + + def normal(self): + """Restaura la expresión del mono. + + Este función se suele ejecutar por si misma, unos + segundos después de haber gritado y sonreir.""" + self.definir_imagen(self.image_normal) + + def decir(self, mensaje): + """Emite un mensaje y además sonrie mientras habla. + + Por ejemplo: + + >>> mono.decir("Estoy hablando!!!") + + .. image:: images/actores/mono_dice.png + """ + self.sonreir() + Actor.decir(self, mensaje) diff --git a/pilas/actores/nave.py b/pilas/actores/nave.py new file mode 100644 index 0000000..b2ac3bb --- /dev/null +++ b/pilas/actores/nave.py @@ -0,0 +1,79 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +import pilas +from pilas.actores import Animacion +import math + +class Nave(Animacion): + "Representa una nave que puede disparar." + + def __init__(self, x=0, y=0, velocidad=2): + self.velocidad = velocidad + grilla = pilas.imagenes.cargar_grilla("nave.png", 2) + Animacion.__init__(self, grilla, ciclica=True, x=x, y=y) + self.radio_de_colision = 20 + self.aprender(pilas.habilidades.PuedeExplotar) + self.contador_frecuencia_disparo = 0 + self.disparos = [] + + def actualizar(self): + Animacion.actualizar(self) + + if pilas.mundo.control.izquierda: + self.rotacion -= self.velocidad + elif pilas.mundo.control.derecha: + self.rotacion += self.velocidad + + if pilas.mundo.control.arriba: + self.avanzar() + + self.contador_frecuencia_disparo += 1 + + if pilas.mundo.control.boton: + if self.contador_frecuencia_disparo > 10: + self.contador_frecuencia_disparo = 0 + self.disparar() + + self.eliminar_disparos_innecesarios() + + def eliminar_disparos_innecesarios(self): + for d in list(self.disparos): + if d.x < -320 or d.x > 320 or d.y < -240 or d.y > 240: + d.eliminar() + self.disparos.remove(d) + + + def disparar(self): + "Hace que la nave dispare." + disparo_nuevo = pilas.actores.Disparo(self.x, self.y, self.rotacion, 4) + self.disparos.append(disparo_nuevo) + + def avanzar(self): + "Hace avanzar la nave en direccion a su angulo." + rotacion_en_radianes = math.radians(-self.rotacion + 90) + dx = math.cos(rotacion_en_radianes) * self.velocidad + dy = math.sin(rotacion_en_radianes) * self.velocidad + self.x += dx + self.y += dy + + def definir_enemigos(self, grupo, cuando_elimina_enemigo=None): + """hace que una nave tenga como enemigos a todos los actores del grupo. + + El argumento cuando_elimina_enemigo tiene que ser una funcion que + se ejecutara cuando se produzca la colision.""" + self.cuando_elimina_enemigo = cuando_elimina_enemigo + pilas.mundo.colisiones.agregar(self.disparos, grupo, self.hacer_explotar_al_enemigo) + + def hacer_explotar_al_enemigo(self, mi_disparo, el_enemigo): + "Es el método que se invoca cuando se produce una colisión 'tiro <-> enemigo'" + mi_disparo.eliminar() + el_enemigo.eliminar() + + if self.cuando_elimina_enemigo: + self.cuando_elimina_enemigo() diff --git a/pilas/actores/opcion.py b/pilas/actores/opcion.py new file mode 100644 index 0000000..2813525 --- /dev/null +++ b/pilas/actores/opcion.py @@ -0,0 +1,36 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +import pilas +from pilas.actores import Texto + +class Opcion(Texto): + + def __init__(self, texto, x=0, y=0, funcion_a_invocar=None): + Texto.__init__(self, texto, x=x, y=y) + self.magnitud = 20 + self.funcion_a_invocar = funcion_a_invocar + self.color = pilas.colores.gris + self.z = -300 + self.centro = ("centro", "centro") + + def resaltar(self, estado=True): + "Pinta la opcion actual de un color mas claro." + + if estado: + self.color = pilas.colores.blanco + else: + self.color = pilas.colores.gris + + def seleccionar(self): + "Invoca a la funcion que tiene asociada para ejecutar." + + if self.funcion_a_invocar: + self.funcion_a_invocar() + else: + print "Cuidado, la opcion", self, "no tiene funcion asociada." diff --git a/pilas/actores/pausa.py b/pilas/actores/pausa.py new file mode 100644 index 0000000..6339058 --- /dev/null +++ b/pilas/actores/pausa.py @@ -0,0 +1,17 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +import pilas +from pilas.actores import Actor + +class Pausa(Actor): + + def __init__(self, x=0, y=0): + Actor.__init__(self, x=x, y=y) + self.centro = ('centro', 'centro') + self.imagen = "icono_pausa.png" diff --git a/pilas/actores/pelota.py b/pilas/actores/pelota.py new file mode 100644 index 0000000..88cee25 --- /dev/null +++ b/pilas/actores/pelota.py @@ -0,0 +1,23 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +from pilas.actores import Actor +import pilas + +class Pelota(Actor): + "Representa una pelota de Volley." + + def __init__(self, x=0, y=0): + imagen = pilas.imagenes.cargar('pelota.png') + Actor.__init__(self, imagen) + self.rotacion = 0 + self.x = x + self.y = y + self.radio_de_colision = 25 + + self.aprender(pilas.habilidades.RebotarComoPelota) diff --git a/pilas/actores/piedra.py b/pilas/actores/piedra.py new file mode 100644 index 0000000..9a2d2d1 --- /dev/null +++ b/pilas/actores/piedra.py @@ -0,0 +1,36 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +from pilas.actores import Actor +import pilas + +class Piedra(Actor): + "Representa un bloque que tiene fisica como una caja." + + def __init__(self, x=0, y=0, tamano="grande", dx=0, dy=0): + imagen = pilas.imagenes.cargar('piedra_' + tamano + '.png') + Actor.__init__(self, imagen) + self.rotacion = 0 + self.x = x + self.y = y + self.dx = dx + self.dy = dy + + radios = { + 'grande': 25, + 'media': 20, + 'chica': 10, + } + + self.radio_de_colision = radios[tamano] + self.aprender(pilas.habilidades.SeMantieneEnPantalla) + + def actualizar(self): + self.rotacion += 1 + self.x += self.dx + self.y += self.dy diff --git a/pilas/actores/pingu.py b/pilas/actores/pingu.py new file mode 100644 index 0000000..1b9edcb --- /dev/null +++ b/pilas/actores/pingu.py @@ -0,0 +1,104 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +import pilas +from pilas.actores import Actor +from pilas.comportamientos import Comportamiento + +VELOCIDAD = 4 + +class Pingu(Actor): + """Muestra a un pingüino que sabe caminar con el teclado. + + .. image:: images/actores/pingu.png + + Este actor responde al teclado, así que podremos + usar los direccionales del teclado ``izquierda``, ``arriba`` + y ``derecha``: + + >>> pingu = pilas.actores.Pingu() + """ + + def __init__(self, x=0, y=0): + Actor.__init__(self, x=x, y=y) + self.imagen = pilas.imagenes.cargar_grilla("pingu.png", 10) + self.definir_cuadro(4) + self.hacer(Esperando()) + self.radio_de_colision = 30 + self.centro = ("centro", "abajo") + + def definir_cuadro(self, indice): + self.imagen.definir_cuadro(indice) + + +class Esperando(Comportamiento): + "Un actor en posicion normal o esperando a que el usuario pulse alguna tecla." + + def iniciar(self, receptor): + self.receptor = receptor + self.receptor.definir_cuadro(4) + + def actualizar(self): + if pilas.mundo.control.izquierda: + self.receptor.hacer(Caminando()) + elif pilas.mundo.control.derecha: + self.receptor.hacer(Caminando()) + + if pilas.mundo.control.arriba: + self.receptor.hacer(Saltando()) + + +class Caminando(Comportamiento): + + def __init__(self): + self.cuadros = [5, 5, 6, 6, 7, 7, 8, 8, 9, 9] + self.paso = 0 + + def actualizar(self): + self.avanzar_animacion() + + if pilas.mundo.control.izquierda: + self.receptor.x -= VELOCIDAD + elif pilas.mundo.control.derecha: + self.receptor.x += VELOCIDAD + else: + self.receptor.hacer(Esperando()) + + if pilas.mundo.control.arriba: + self.receptor.hacer(Saltando()) + + def avanzar_animacion(self): + self.paso += 1 + + if self.paso >= len(self.cuadros): + self.paso = 0 + + self.receptor.definir_cuadro(self.cuadros[self.paso]) + +class Saltando(Comportamiento): + + def __init__(self): + self.dy = 10 + + def iniciar(self, receptor): + self.receptor = receptor + self.receptor.definir_cuadro(0) + self.origen = self.receptor.y + + def actualizar(self): + self.receptor.y += self.dy + self.dy -= 0.3 + + if self.receptor.y < self.origen: + self.receptor.y = self.origen + self.receptor.hacer(Esperando()) + + if pilas.mundo.control.izquierda: + self.receptor.x -= VELOCIDAD + elif pilas.mundo.control.derecha: + self.receptor.x += VELOCIDAD diff --git a/pilas/actores/pizarra.py b/pilas/actores/pizarra.py new file mode 100644 index 0000000..8fea8ba --- /dev/null +++ b/pilas/actores/pizarra.py @@ -0,0 +1,65 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +import pilas +from pilas.actores import Actor +from pilas import colores + +class Pizarra(Actor): + """Representa una superficie de dibujo inicialmente transparente. + + Puedes pintar sobre esta pizarra usando métodos que simulan + un lapiz, que se puede mover sobre una superficie. + """ + + def __init__(self, x=0, y=0, ancho=None, alto=None): + # Si no define area de la pizarra toma el tamano de la ventana. + if not ancho or not alto: + ancho, alto = pilas.mundo.motor.obtener_area() + + Actor.__init__(self, x=x, y=y) + self.imagen = pilas.imagenes.cargar_superficie(ancho, alto) + + def dibujar_punto(self, x, y, color=colores.negro): + x, y = self.obtener_coordenada_fisica(x, y) + self.imagen.dibujar_punto(x, y, color=color) + + def obtener_coordenada_fisica(self, x, y): + x = (self.imagen.ancho()/2) + x + y = (self.imagen.alto()/2) - y + return x, y + + def pintar_imagen(self, imagen, x, y): + self.pintar_parte_de_imagen(imagen, 0, 0, imagen.ancho(), imagen.alto(), x, y) + + def pintar_parte_de_imagen(self, imagen, origen_x, origen_y, ancho, alto, x, y): + x, y = self.obtener_coordenada_fisica(x, y) + self.imagen.pintar_parte_de_imagen(imagen, origen_x, origen_y, ancho, alto, x, y) + + def pintar_grilla(self, grilla, x, y): + grilla.dibujarse_sobre_una_pizarra(self, x, y) + + def pintar(self, color): + self.imagen.pintar(color) + + def linea(self, x, y, x2, y2, color=colores.negro, grosor=1): + x, y = self.obtener_coordenada_fisica(x, y) + x2, y2 = self.obtener_coordenada_fisica(x2, y2) + self.imagen.linea(x, y, x2, y2, color, grosor) + + def rectangulo(self, x, y, ancho, alto, color=colores.negro, relleno=False, grosor=1): + x, y = self.obtener_coordenada_fisica(x, y) + self.imagen.rectangulo(x, y, ancho, alto, color, relleno, grosor) + + def texto(self, cadena, x=0, y=0, magnitud=10, fuente=None, color=colores.negro): + x, y = self.obtener_coordenada_fisica(x, y) + self.imagen.texto(cadena, x, y, magnitud, fuente, color) + + def poligono(self, puntos, color=pilas.colores.negro, grosor=1): + puntos = [self.obtener_coordenada_fisica(*p) for p in puntos] + self.imagen.poligono(puntos, color, grosor) diff --git a/pilas/actores/puntaje.py b/pilas/actores/puntaje.py new file mode 100644 index 0000000..6be73e6 --- /dev/null +++ b/pilas/actores/puntaje.py @@ -0,0 +1,30 @@ +# -*- encoding: utf-8 -*- +# For Pilas engine - A video game framework. +# +# Copyright 2010 - Pablo Garrido +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar +# + + +import pilas +from pilas.actores import Texto + +class Puntaje(Texto): + """Representa un contador de Puntaje""" + + def __init__(self, texto='0', x=0, y=0, color=pilas.colores.negro): + Texto.__init__(self, texto, x=x, y=y) + self.color = color + + def definir(self, puntaje_variable = '0'): + self.puntaje_texto = str(puntaje_variable) + self.texto = self.puntaje_texto + + def aumentar(self, cantidad=1): + self.definir(int(self.texto) + int(cantidad)) + + def obtener(self): + return int(self.texto) + diff --git a/pilas/actores/temporizador.py b/pilas/actores/temporizador.py new file mode 100644 index 0000000..88606f0 --- /dev/null +++ b/pilas/actores/temporizador.py @@ -0,0 +1,66 @@ +# -*- encoding: utf-8 -*- +# For Pilas engine - A video game framework. +# +# Copyright 2010 - Pablo Garrido +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar +# + +import pilas +from pilas.actores import Texto +from pilas import colores + +class Temporizador(Texto): + """Representa un contador de tiempo con cuenta regresiva. + + Por ejemplo: + + >>> t = pilas.actores.Temporizador() + >>> def hola_mundo(): + ... pilas.avisar("Hola mundo, pasaron 10 segundos...") + ... + >>> t.ajustar(10, hola_mundo) + >>> t.iniciar() + + """ + def __init__(self, x=0, y=0, color=colores.negro): + Texto.__init__(self, '0', x=x, y=y) + self.ajustar(1, self.funcion_vacia) + self.color = color + + # funcion cuando no se ajusta temporizador + def funcion_vacia(self): + pass + + def definir_tiempo_texto(self, variable): + self.texto = str(variable) + + # con la funcion ajustar manipulamos el tiempo y la + # funcion queremos ejecutar + def ajustar(self, tiempo=1, funcion=None): + """Indica una funcion para ser invocada en el tiempo indicado. + + La función no tiene que recibir parámetros, y luego de + ser indicada se tiene que iniciar el temporizador. + """ + + self.tiempo = tiempo + self.definir_tiempo_texto(self.tiempo) + + if funcion == None: + self.funcion = self.funcion_vacia() + else: + self.funcion = funcion + + def _restar_a_contador(self): + if self.tiempo != 0: + self.tiempo -= 1 + self.definir_tiempo_texto(self.tiempo) + return True + + def iniciar(self): + """Inicia el contador de tiempo con la función indicada.""" + pilas.mundo.agregar_tarea_una_vez(self.tiempo, self.funcion) + pilas.mundo.agregar_tarea_siempre(1, self._restar_a_contador) + diff --git a/pilas/actores/texto.py b/pilas/actores/texto.py new file mode 100644 index 0000000..3fbe193 --- /dev/null +++ b/pilas/actores/texto.py @@ -0,0 +1,60 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +import pilas +from actor import Actor + +class Texto(Actor): + """Representa un texto en pantalla. + + El texto tiene atributos como ``texto``, ``magnitud`` y ``color``, por + ejemplo para crear un mensaje de saludo podríamos escribir: + + >>> saludo = pilas.actores.Texto("Hola mundo!") + + + """ + + def __init__(self, texto="None", x=0, y=0, magnitud=20): + imagen = pilas.mundo.motor.obtener_texto(texto, magnitud) + self._definir_area_de_texto(texto, magnitud) + Actor.__init__(self, imagen, x=x, y=y) + self.magnitud = magnitud + self.texto = texto + self.color = pilas.colores.blanco + self.centro = ("centro", "centro") + self.fijo = True + + def obtener_texto(self): + return self.imagen.texto + + def definir_texto(self, texto): + self.imagen.texto = texto + self._definir_area_de_texto(texto, self.magnitud) + + texto = property(obtener_texto, definir_texto, doc="El texto que se tiene que mostrar.") + + def obtener_magnitud(self): + return self.imagen.magnitud + + def definir_magnitud(self, magnitud): + self._magnitud = magnitud + self.imagen.magnitud = magnitud + + magnitud = property(obtener_magnitud, definir_magnitud, doc="El tamaño del texto.") + + def obtener_color(self): + return self.imagen.color + + def definir_color(self, color): + self.imagen.color = color + + color = property(obtener_color, definir_color, doc="Color del texto.") + + def _definir_area_de_texto(self, texto, magnitud): + self._ancho, self._alto = pilas.mundo.motor.obtener_area_de_texto(texto, magnitud) diff --git a/pilas/actores/tortuga.py b/pilas/actores/tortuga.py new file mode 100644 index 0000000..e6cf55c --- /dev/null +++ b/pilas/actores/tortuga.py @@ -0,0 +1,99 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +import pilas +from pilas.actores import Actor + + +class Tortuga(Actor): + "Representa una tortuga que se mueve por la pantalla como la tortuga de Logo." + + def __init__(self, x=0, y=0, dibuja=True): + self.pizarra = pilas.actores.Pizarra() + + imagen = pilas.imagenes.cargar('tortuga.png') + Actor.__init__(self, imagen, x=x, y=y) + + self.rotacion = 0 + self.velocidad = 6 + + self.anterior_x = x + self.anterior_y = y + + if dibuja: + self.bajalapiz() + else: + self.subelapiz() + + self.color = pilas.colores.negro + + def avanzar(self, pasos): + self.hacer_luego(pilas.comportamientos.Avanzar(pasos, self.velocidad)) + + def giraderecha(self, delta): + self.hacer_luego(pilas.comportamientos.Girar(abs(delta), self.velocidad)) + + def giraizquierda(self, delta): + self.hacer_luego(pilas.comportamientos.Girar(-abs(delta), self.velocidad)) + + def actualizar(self): + if self.anterior_x != self.x or self.anterior_y != self.y: + self.dibujar_linea_desde_el_punto_anterior() + self.anterior_x = self.x + self.anterior_y = self.y + + def dibujar_linea_desde_el_punto_anterior(self): + self.pizarra.linea(self.anterior_x, self.anterior_y, self.x, self.y, self.color, grosor=4) + + def bajalapiz(self): + self.lapiz_bajo = True + + def subelapiz(self): + self.lapiz_bajo = False + + def pon_color(self, color): + self.color = color + + def crear_poligono(self, lados = 4, escala = 100, sentido = -1): + "dibuja un poligono de n lados" + for i in range(lados): + rotacion = 360 / lados + self.avanzar(escala) + if sentido == 1: + self.giraderecha(rotacion) + else: + self.giraizquierda(rotacion) + + def crear_circulo(self, radio = 30, sentido = -1): + "dibuja un circulo" + for i in range(36): + self.avanzar(radio) + if sentido == 1: + self.giraderecha(10) + else: + self.giraizquierda(10) + + # Alias de metodos + av = avanzar + gd = giraderecha + gi = giraizquierda + bl = bajalapiz + sl = subelapiz + pc = pon_color + + + def get_color(self): + return self._color + + def set_color(self, color): + self._color = color + + color = property(get_color, set_color) + + def pintar(self, color=None): + self.pizarra.pintar(color) diff --git a/pilas/actores/utils.py b/pilas/actores/utils.py new file mode 100644 index 0000000..da064dc --- /dev/null +++ b/pilas/actores/utils.py @@ -0,0 +1,70 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +import random +import pilas + +def ordenar_actores_por_valor_z(): + "Ordena todos los actores para que se impriman con 'z' como criterio de orden." + pilas.actores.todos.sort() + +def insertar_como_nuevo_actor(actor): + "Coloca a un actor en la lista de actores a imprimir en pantalla." + pilas.actores.todos.append(actor) + +def eliminar_un_actor(actor): + try: + pilas.actores.todos.remove(actor) + except ValueError: + #TODO: quitar este silenciador de excepcion. + pass + +def eliminar_a_todos(): + a_eliminar = list(pilas.actores.todos) + a_eliminar = a_eliminar[1:] # evita borrar el fondo. + + for x in a_eliminar: + x.eliminar() + +def destruir_a_todos(): + "Elimina a los actores inmediatamente (evita que exploten o hagan algo)." + a_eliminar = list(pilas.actores.todos) + + for x in a_eliminar: + x.destruir() + +def obtener_actor_en(x, y): + "Intenta obtener el actor mas cerca de la pantalla (z mas pequeño) en la posición (x, y)" + + # Busca el objeto que colisiones ordenando en sentido inverso. + for sprite in pilas.actores.todos[::-1]: + if sprite.colisiona_con_un_punto(x, y): + return sprite + + return None + + +def fabricar(clase, cantidad=1, posiciones_al_azar=True, *k, **kv): + "Genera muchas intancias de objetos asignando posiciones aleatorias." + + objetos_creados = [] + + for x in range(cantidad): + if posiciones_al_azar: + x = random.randint(-300, 300) + y = random.randint(-200, 200) + else: + x = 0 + y = 0 + + kv['x'] = x + kv['y'] = y + nuevo = clase(*k, **kv) + objetos_creados.append(nuevo) + + return pilas.grupo.Grupo(objetos_creados) diff --git a/pilas/aplicacion.py b/pilas/aplicacion.py new file mode 100644 index 0000000..7e0a9a0 --- /dev/null +++ b/pilas/aplicacion.py @@ -0,0 +1,63 @@ +import sys + +from PyQt4 import QtGui +from PyQt4 import QtCore + +import pilas +from pilas.console import console_widget + + +class Window(QtGui.QWidget): + + def __init__(self, parent=None, pilas_width=320, pilas_height=240): + QtGui.QWidget.__init__(self, parent) + + vbox = QtGui.QVBoxLayout(self) + pilas.iniciar(usar_motor='qt') + ventana_pilas = pilas.mundo.motor + + ventana_pilas.setMinimumWidth(pilas_width) + ventana_pilas.setMinimumHeight(pilas_height) + + horizontalLayout = QtGui.QHBoxLayout() + spacer1 = QtGui.QSpacerItem(58, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + horizontalLayout.addItem(spacer1) + + horizontalLayout.addWidget(ventana_pilas) + + spacer2 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + horizontalLayout.addItem(spacer2) + + vbox.addLayout(horizontalLayout) + + #vbox.addWidget(self.horizontalLayout) + #hbox.addWidget(ventana_pilas) + + #Crear actor + self.mono = pilas.actores.Mono() + pilas.eventos.click_de_mouse.conectar(self.sonreir) + + # Agrega la Consola + locals = {'pilas': pilas, 'mono': self.mono} + self.consoleWidget = console_widget.ConsoleWidget(locals) + + vbox.addWidget(self.consoleWidget) + + #self.ui.ventana = pilas.obtener_widget() + # Agrega un nuevo widget al layout existente. + #label = QtGui.QLabel(self.ui.centralwidget) + #self.ui.layout.addWidget(label) + #label.setText("Hola") + + def sonreir(self, evento): + self.mono.sonreir() + + +def main(): + app = QtGui.QApplication(sys.argv) + ventana = Window() + ventana.show() + app.exec_() + +if __name__ == '__main__': + main() diff --git a/pilas/atajos.py b/pilas/atajos.py new file mode 100644 index 0000000..4e00b6d --- /dev/null +++ b/pilas/atajos.py @@ -0,0 +1,17 @@ +# -*- encoding: utf-8 -*- +# pilas engine - a video game framework. +# +# copyright 2010 - hugo ruscitti +# license: lgplv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# website - http://www.pilas-engine.com.ar + +import pilas + +fabricar = pilas.actores.utils.fabricar + +def crear_grupo(*k): + return pilas.grupos.Grupo(k) + +def definir_gravedad(x=0, y=-900): + pilas.mundo.fisica.definir_gravedad(x, y) diff --git a/pilas/camara.py b/pilas/camara.py new file mode 100644 index 0000000..c013c45 --- /dev/null +++ b/pilas/camara.py @@ -0,0 +1,39 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +import pilas + +class Camara(object): + """Representa el punto de vista de la ventana. + + Los atributos ``x`` e ``y`` indican cual debe ser el + punto central de la pantalla. Por defecto estos + valores con (0, 0).""" + + def __init__(self, motor): + self.motor = motor + + @pilas.utils.interpolable + def _set_x(self, x): + pilas.mundo.motor.definir_centro_de_la_camara(x, self.y) + + def _get_x(self): + x, y = pilas.mundo.motor.obtener_centro_de_la_camara() + return x + + @pilas.utils.interpolable + def _set_y(self, y): + pilas.mundo.motor.definir_centro_de_la_camara(self.x, y) + + def _get_y(self): + x, y = pilas.mundo.motor.obtener_centro_de_la_camara() + return y + + x = property(_get_x, _set_x) + y = property(_get_y, _set_y) + diff --git a/pilas/colisiones.py b/pilas/colisiones.py new file mode 100644 index 0000000..558c396 --- /dev/null +++ b/pilas/colisiones.py @@ -0,0 +1,60 @@ +# -*- encoding: utf-8 -*- +# pilas engine - a video game framework. +# +# copyright 2010 - hugo ruscitti +# license: lgplv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# website - http://www.pilas-engine.com.ar + +import utils +import pilas + +class Colisiones: + "Administra todas las colisiones entre actores." + + def __init__(self): + self.colisiones = [] + + def verificar_colisiones(self): + for x in self.colisiones: + self._verificar_colisiones_en_tupla(x) + + def _verificar_colisiones_en_tupla(self, tupla): + "Toma dos grupos de actores y analiza colisiones entre ellos." + (grupo_a, grupo_b, funcion_a_llamar) = tupla + + for a in grupo_a: + for b in grupo_b: + if id(a) != id(b) and utils.colisionan(a, b): + funcion_a_llamar(a, b) + + # verifica si alguno de los dos objetos muere en la colision. + if a not in pilas.actores.todos: + if a in grupo_a: + list.remove(grupo_a, a) + + if b not in pilas.actores.todos: + if b in grupo_b: + list.remove(grupo_b, b) + + def agregar(self, grupo_a, grupo_b, funcion_a_llamar): + "Agrega dos listas de actores para analizar colisiones." + + if not isinstance(grupo_a, list): + grupo_a = [grupo_a] + + if not isinstance(grupo_b, list): + grupo_b = [grupo_b] + + self.colisiones.append((grupo_a, grupo_b, funcion_a_llamar)) + + def obtener_colisiones(self, actor, grupo_de_actores): + "Retorna una lista de los actores que colisionan con uno en particular." + + lista_de_colisiones = [] + + for a in grupo_de_actores: + if id(actor) != id(a) and utils.colisionan(actor, a): + lista_de_colisiones.append(a) + + return lista_de_colisiones diff --git a/pilas/colores.py b/pilas/colores.py new file mode 100644 index 0000000..c8ec4ce --- /dev/null +++ b/pilas/colores.py @@ -0,0 +1,48 @@ +import pilas + +class Color(object): + "Representa un color en base a 4 componentes." + + def __init__(self, r, g, b, a=255): + self.r = r + self.g = g + self.b = b + self.a = a + + def obtener(self): + return pilas.motor.Color(self.r, self.g, self.b, self.a) + + def __str__(self): + return "" %(self.r, self.g, self.b, self.a) + + def obtener_componentes(self): + return (self.r, self.g, self.b, self.a) + +# Colores principales. +negro = Color(0, 0, 0) +blanco = Color(255, 255, 255) +rojo = Color(255, 0, 0) +verde = Color(0, 255, 0) +azul = Color(0, 0, 255) +gris = Color(128, 128, 128) + +# Colores secundarios +amarillo = Color(255, 255, 0) +magenta = Color(255, 0, 255) +cyan = Color(0, 255, 255) +grisclaro = Color(192, 192, 192) +grisoscuro = Color(100, 100, 100) +verdeoscuro = Color(0, 128, 0) +azuloscuro = Color(0, 0, 128) +naranja = Color(255, 200, 0) +rosa = Color(255, 175, 175) +violeta = Color(128, 0, 255) +marron = Color(153, 102, 0) + +# Colores transparentes +negro = Color(0, 0, 0, 160) +blanco = Color(255, 255, 255, 160) +rojo_transparente = Color(255, 0, 0, 160) +verde_transparente = Color(0, 255, 0, 160) +azul_transparente = Color(0, 0, 255, 160) +gris_transparente = Color(128, 128, 128, 160) diff --git a/pilas/comportamientos.py b/pilas/comportamientos.py new file mode 100644 index 0000000..7fb486e --- /dev/null +++ b/pilas/comportamientos.py @@ -0,0 +1,107 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +import math +import pilas + +class Comportamiento(object): + "Representa un comportamiento (estrategia) que se puede anexar a un actor." + + def iniciar(self, receptor): + "Se invoca cuando se anexa el comportamiento a un actor." + self.receptor = receptor + + def actualizar(self): + """Actualiza el comportamiento en un instante dado. + + Si este metodo retorna True entonces el actor dejará + de ejecutar este comportamiento.""" + pass + + def terminar(self): + pass + + +class Girar(Comportamiento): + "Hace girar constantemente al actor respecto de su eje de forma relativa." + + def __init__(self, delta, velocidad): + self.delta = delta + + if delta > 0: + self.velocidad = velocidad + else: + self.velocidad = -velocidad + + + def iniciar(self, receptor): + "Define el angulo inicial." + self.receptor = receptor + self.angulo_final = (receptor.rotacion + self.delta) % 360 + + def actualizar(self): + self.receptor.rotacion += self.velocidad + + delta = abs(self.receptor.rotacion - self.angulo_final) + + if delta <= abs(self.velocidad): + self.receptor.rotacion = self.angulo_final + return True + +class Saltar(Comportamiento): + + def __init__(self, velocidad_inicial=10): + self.velocidad_inicial = velocidad_inicial + + def iniciar(self, receptor): + self.receptor = receptor + self.suelo = int(self.receptor.y) + self.velocidad = self.velocidad_inicial + + def actualizar(self): + self.receptor.y += self.velocidad + self.velocidad -= 0.3 + + if self.receptor.y <= self.suelo: + self.velocidad_inicial /= 2.0 + self.velocidad = self.velocidad_inicial + + if self.velocidad_inicial <= 1: + # Si toca el suelo + self.receptor.y = self.suelo + return True + + +class Avanzar(Comportamiento): + "Desplaza al actor en la dirección y sentido indicado por una rotación." + + def __init__(self, pasos, velocidad=5): + self.pasos = abs(pasos) + self.velocidad = velocidad + + def iniciar(self, receptor): + self.receptor = receptor + rotacion_en_radianes = math.radians(-receptor.rotacion) + self.dx = math.cos(rotacion_en_radianes) + self.dy = math.sin(rotacion_en_radianes) + + def actualizar(self): + salir = False + + if self.pasos - self.velocidad < 0: + avance = self.pasos + salir = True + else: + avance = self.velocidad + + self.pasos -= avance + self.receptor.x += self.dx * avance + self.receptor.y += self.dy * avance + + if salir: + return True diff --git a/pilas/console/__init__.py b/pilas/console/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/pilas/console/__init__.py diff --git a/pilas/console/console.py b/pilas/console/console.py new file mode 100644 index 0000000..975a9a6 --- /dev/null +++ b/pilas/console/console.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import + +import sys +from code import InteractiveConsole + + +class Cache(object): + """Replace stdout and stderr behavior in order to collect outputs.""" + + def __init__(self): + self.reset() + + def reset(self): + """Clean the cache.""" + self.out = [] + + def write(self, line): + """Collect the output into cache to be accesed later.""" + self.out.append(line) + + def flush(self): + """Join together all the outputs and return it to be displayed.""" + if len(self.out) > 1: + output = ''.join(self.out)[:-1] + self.reset() + return output + + +class Console(InteractiveConsole): + """Work as a Python Console.""" + + def __init__(self, locals): + InteractiveConsole.__init__(self, locals) + self.stdout = sys.stdout + self.stderr = sys.stderr + self._cache = Cache() + self.output = '' + + def get_output(self): + """Replace out and error channels with cache.""" + sys.stdout = self._cache + sys.stderr = self._cache + + def return_output(self): + """Reassign the proper values to output and error channel.""" + sys.stdout = self.stdout + sys.stderr = self.stderr + + def push(self, line): + """Insert a command into the console.""" + if line in ('exit()', 'help()'): + return + self.get_output() + val = InteractiveConsole.push(self, line) + self.return_output() + self.output = self._cache.flush() + return val diff --git a/pilas/console/console_widget.py b/pilas/console/console_widget.py new file mode 100644 index 0000000..c96d702 --- /dev/null +++ b/pilas/console/console_widget.py @@ -0,0 +1,329 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import + +import re + +from PyQt4.QtGui import QPlainTextEdit +from PyQt4.QtGui import QTextCursor +from PyQt4.QtGui import QTextFormat +from PyQt4.QtGui import QTextEdit +from PyQt4.QtGui import QColor +from PyQt4.QtCore import Qt +from PyQt4.QtCore import SIGNAL + +from pilas.console import console +from pilas.console import highlighter + + +INDENT = 4 + +BRACES = {"'": "'", + '"': '"', + '{': '}', + '[': ']', + '(': ')'} + +EDITOR_STYLE = """QPlainTextEdit { + font-family: monospace; + font-size: 10; + color: black; + background-color: white; + selection-color: white; + selection-background-color: #437DCD; + }""" + + +class ConsoleWidget(QPlainTextEdit): + + def __init__(self, locals): + QPlainTextEdit.__init__(self, u'>>> ') + self.setUndoRedoEnabled(False) + self.setStyleSheet(EDITOR_STYLE) + self.setToolTip(self.tr("Show/Hide (F4)")) + + self._patIsWord = re.compile('\w+') + self.prompt = u'>>> ' + self._console = console.Console(locals) + self._history = [] + self._braces = None + + self._highlighter = highlighter.Highlighter(self.document(), 'python', + highlighter.COLOR_SCHEME) + + self.connect(self, SIGNAL("cursorPositionChanged()"), + self.highlight_current_line) + self.highlight_current_line() + self.setCursorPosition(0) + + def setCursorPosition(self, position): + self.moveCursor(QTextCursor.StartOfLine) + for i in xrange(len(self.prompt) + position): + self.moveCursor(QTextCursor.Right) + + def keyPressEvent(self, event): + if event.key() in (Qt.Key_Enter, Qt.Key_Return): + self._write_command() + return + if self._get_cursor_position() < 0: + self.setCursorPosition(0) + if event.key() == Qt.Key_Tab: + self.textCursor().insertText(' ' * INDENT) + return + if event.key() == Qt.Key_Home: + self.setCursorPosition(0) + return + if event.key() == Qt.Key_PageUp: + return + elif event.key() in (Qt.Key_Left, Qt.Key_Backspace): + if self._get_cursor_position() == 0: + return + elif event.key() == Qt.Key_Up: + self._set_command(self._get_prev_history_entry()) + return + elif event.key() == Qt.Key_Down: + self._set_command(self._get_next_history_entry()) + return + + if event.key() == Qt.Key_Tab: + if self.textCursor().hasSelection(): + self.indent_more() + return + else: + self.textCursor().insertText(' ' * INDENT) + return + elif event.key() == Qt.Key_Backspace: + if self.textCursor().hasSelection(): + QPlainTextEdit.keyPressEvent(self, event) + return + for i in xrange(INDENT): + self.moveCursor(QTextCursor.Left, QTextCursor.KeepAnchor) + text = self.textCursor().selection() + if unicode(text.toPlainText()) == ' ' * INDENT: + self.textCursor().removeSelectedText() + return + else: + for i in xrange(text.toPlainText().size()): + self.moveCursor(QTextCursor.Right) + elif event.key() == Qt.Key_Home: + if event.modifiers() == Qt.ShiftModifier: + move = QTextCursor.KeepAnchor + else: + move = QTextCursor.MoveAnchor + if self.textCursor().atBlockStart(): + self.moveCursor(QTextCursor.WordRight, move) + return + elif event.key() in (Qt.Key_Enter, Qt.Key_Return) and \ + event.modifiers() == Qt.ShiftModifier: + return + elif unicode(event.text()) in \ + (set(BRACES.values()) - set(["'", '"'])): + self.moveCursor(QTextCursor.Left, QTextCursor.KeepAnchor) + brace = unicode(self.textCursor().selection().toPlainText()) + self.moveCursor(QTextCursor.Right) + self.moveCursor(QTextCursor.Right, QTextCursor.KeepAnchor) + braceClose = unicode(self.textCursor().selection().toPlainText()) + self.moveCursor(QTextCursor.Left) + if BRACES.get(brace, False) == unicode(event.text()) and \ + braceClose == unicode(event.text()): + self.moveCursor(QTextCursor.Right) + return + selection = self.textCursor().selectedText() + + QPlainTextEdit.keyPressEvent(self, event) + + if unicode(event.text()) in BRACES: + cursor = self.textCursor() + cursor.movePosition(QTextCursor.StartOfLine, + QTextCursor.KeepAnchor) + self.textCursor().insertText( + BRACES[unicode(event.text())]) + self.moveCursor(QTextCursor.Left) + self.textCursor().insertText(selection) + + def highlight_current_line(self): + self.emit(SIGNAL("cursorPositionChange(int, int)"), + self.textCursor().blockNumber() + 1, + self.textCursor().columnNumber()) + self.extraSelections = [] + + selection = QTextEdit.ExtraSelection() + lineColor = QColor(highlighter.COLOR_SCHEME.get('current-line', + highlighter.COLOR_SCHEME['current-line'])) + lineColor.setAlpha(20) + selection.format.setBackground(lineColor) + selection.format.setProperty(QTextFormat.FullWidthSelection, + True) + selection.cursor = self.textCursor() + selection.cursor.clearSelection() + self.extraSelections.append(selection) + self.setExtraSelections(self.extraSelections) + + #Brace matching + if self._braces is not None: + self._braces = None + cursor = self.textCursor() + if cursor.position() == 0: + self.setExtraSelections(self.extraSelections) + return + cursor.movePosition(QTextCursor.PreviousCharacter, + QTextCursor.KeepAnchor) + text = unicode(cursor.selectedText()) + pos1 = cursor.position() + if text in (')', ']', '}'): + pos2 = self._match_braces(pos1, text, forward=False) + elif text in ('(', '[', '{'): + pos2 = self._match_braces(pos1, text, forward=True) + else: + self.setExtraSelections(self.extraSelections) + return + if pos2 is not None: + self._braces = (pos1, pos2) + selection = QTextEdit.ExtraSelection() + selection.format.setForeground(QColor( + highlighter.COLOR_SCHEME.get('brace-foreground', + highlighter.COLOR_SCHEME.get('brace-foreground')))) + selection.format.setBackground(QColor( + highlighter.COLOR_SCHEME.get('brace-background', + highlighter.COLOR_SCHEME.get('brace-background')))) + selection.cursor = cursor + self.extraSelections.append(selection) + selection = QTextEdit.ExtraSelection() + selection.format.setForeground(QColor( + highlighter.COLOR_SCHEME.get('brace-foreground', + highlighter.COLOR_SCHEME.get('brace-foreground')))) + selection.format.setBackground(QColor( + highlighter.COLOR_SCHEME.get('brace-background', + highlighter.COLOR_SCHEME.get('brace-background')))) + selection.cursor = self.textCursor() + selection.cursor.setPosition(pos2) + selection.cursor.movePosition(QTextCursor.NextCharacter, + QTextCursor.KeepAnchor) + self.extraSelections.append(selection) + else: + self._braces = (pos1,) + selection = QTextEdit.ExtraSelection() + selection.format.setBackground(QColor( + highlighter.COLOR_SCHEME.get('brace-background', + highlighter.COLOR_SCHEME.get('brace-background')))) + selection.format.setForeground(QColor( + highlighter.COLOR_SCHEME.get('brace-foreground', + highlighter.COLOR_SCHEME.get('brace-foreground')))) + selection.cursor = cursor + self.extraSelections.append(selection) + self.setExtraSelections(self.extraSelections) + + def _text_under_cursor(self): + tc = self.textCursor() + tc.select(QTextCursor.WordUnderCursor) + return tc.selectedText() + + def get_selection(self, posStart, posEnd): + cursor = self.textCursor() + cursor.setPosition(posStart) + cursor2 = self.textCursor() + if posEnd == QTextCursor.End: + cursor2.movePosition(posEnd) + cursor.setPosition(cursor2.position(), QTextCursor.KeepAnchor) + else: + cursor.setPosition(posEnd, QTextCursor.KeepAnchor) + text = cursor.selectedText() + return unicode(text) + + def _match_braces(self, position, brace, forward): + """based on: http://gitorious.org/khteditor""" + if forward: + braceMatch = {'(': ')', '[': ']', '{': '}'} + text = self.get_selection(position, QTextCursor.End) + braceOpen, braceClose = 1, 1 + else: + braceMatch = {')': '(', ']': '[', '}': '{'} + text = self.get_selection(QTextCursor.Start, position) + braceOpen, braceClose = len(text) - 1, len(text) - 1 + while True: + if forward: + posClose = text.find(braceMatch[brace], braceClose) + else: + posClose = text.rfind(braceMatch[brace], 0, braceClose + 1) + if posClose > -1: + if forward: + braceClose = posClose + 1 + posOpen = text.find(brace, braceOpen, posClose) + else: + braceClose = posClose - 1 + posOpen = text.rfind(brace, posClose, braceOpen + 1) + if posOpen > -1: + if forward: + braceOpen = posOpen + 1 + else: + braceOpen = posOpen - 1 + else: + if forward: + return position + posClose + else: + return position - (len(text) - posClose) + else: + return + + def _add_prompt(self, incomplete): + if incomplete: + prompt = '.' * 3 + ' ' + else: + prompt = self.prompt + self.appendPlainText(prompt) + self.moveCursor(QTextCursor.End) + + def _get_cursor_position(self): + return self.textCursor().columnNumber() - len(self.prompt) + + def _write_command(self): + command = self.document().findBlockByLineNumber( + self.document().lineCount() - 1).text() + #remove the prompt from the QString + command = command.remove(0, len(self.prompt)).toUtf8().data() + self._add_history(command.decode('utf8')) + incomplete = self._write(command.decode('utf8')) + if not incomplete: + output = self._read() + if output is not None: + if output.__class__.__name__ == 'unicode': + output = output.encode('utf8') + self.appendPlainText(output.decode('utf8')) + self._add_prompt(incomplete) + + def _set_command(self, command): + self.moveCursor(QTextCursor.End) + self.moveCursor(QTextCursor.StartOfLine, QTextCursor.KeepAnchor) + for i in xrange(len(self.prompt)): + self.moveCursor(QTextCursor.Right, QTextCursor.KeepAnchor) + self.textCursor().removeSelectedText() + self.textCursor().insertText(command) + self.moveCursor(QTextCursor.End) + + def mousePressEvent(self, event): + #to avoid selection + event.ignore() + + def _write(self, line): + return self._console.push(line) + + def _read(self): + return self._console.output + + def _add_history(self, command): + if command and (not self._history or self._history[-1] != command): + self._history.append(command) + self.history_index = len(self._history) + + def _get_prev_history_entry(self): + if self._history: + self.history_index = max(0, self.history_index - 1) + return self._history[self.history_index] + return '' + + def _get_next_history_entry(self): + if self._history: + hist_len = len(self._history) + self.history_index = min(hist_len, self.history_index + 1) + if self.history_index < hist_len: + return self._history[self.history_index] + return '' diff --git a/pilas/console/highlighter.py b/pilas/console/highlighter.py new file mode 100644 index 0000000..a17ab20 --- /dev/null +++ b/pilas/console/highlighter.py @@ -0,0 +1,299 @@ +#-*-coding:utf-8-*- +from __future__ import absolute_import +# based on Python Syntax highlighting from: +# http://diotavelli.net/PyQtWiki/Python%20syntax%20highlighting + +import os +try: + import json +except ImportError: + import simplejson as json + +from PyQt4.QtGui import QColor +from PyQt4.QtGui import QTextCharFormat +from PyQt4.QtGui import QFont +from PyQt4.QtGui import QSyntaxHighlighter +from PyQt4.QtCore import QRegExp + + +COLOR_SCHEME = { + "keyword": "darkMagenta", + "operator": "darkRed", + "brace": "#858585", + "definition": "black", + "string": "green", + "string2": "darkGreen", + "comment": "gray", + "properObject": "darkBlue", + "numbers": "brown", + "spaces": "#BFBFBF", + "extras": "orange", + "editor-background": "white", + "editor-selection-color": "white", + "editor-selection-background": "#437DCD", + "editor-text": "black", + "current-line": "darkCyan", + "selected-word": "yellow", + "brace-background": "#5BC85B", + "brace-foreground": "red"} +FONT_FAMILY = 'Monospace' +FONT_SIZE = 11 +SYNTAX = {} + + +def load_syntax(): + syntax_file = os.path.join(os.path.dirname(__file__), 'lang', + 'python.json') + structure = None + read = open(syntax_file, 'r') + structure = json.load(read) + read.close() + SYNTAX['python'] = structure + + +def format(color, style=''): + """Return a QTextCharFormat with the given attributes.""" + _color = QColor() + _color.setNamedColor(color) + + _format = QTextCharFormat() + _format.setFontFamily(FONT_FAMILY) + _format.setForeground(_color) + if 'bold' in style: + _format.setFontWeight(QFont.Bold) + if 'italic' in style: + _format.setFontItalic(True) + + return _format + + +# Syntax styles that can be shared by all languages +STYLES = { + 'keyword': format(COLOR_SCHEME['keyword'], 'bold'), + 'operator': format(COLOR_SCHEME['operator']), + 'brace': format(COLOR_SCHEME['brace']), + 'definition': format(COLOR_SCHEME['definition'], 'bold'), + 'string': format(COLOR_SCHEME['string']), + 'string2': format(COLOR_SCHEME['string2']), + 'comment': format(COLOR_SCHEME['comment'], 'italic'), + 'properObject': format(COLOR_SCHEME['properObject'], 'italic'), + 'numbers': format(COLOR_SCHEME['numbers']), + 'spaces': format(COLOR_SCHEME['spaces']), + 'extras': format(COLOR_SCHEME['extras'])} + + +def restyle(scheme): + STYLES['keyword'] = format(scheme.get('keyword', + COLOR_SCHEME['keyword']), 'bold') + STYLES['operator'] = format(scheme.get('operator', + COLOR_SCHEME['operator'])) + STYLES['brace'] = format(scheme.get('brace', + COLOR_SCHEME['brace'])) + STYLES['definition'] = format(scheme.get('definition', + COLOR_SCHEME['definition']), 'bold') + STYLES['string'] = format(scheme.get('string', + COLOR_SCHEME['string'])) + STYLES['string2'] = format(scheme.get('string2', + COLOR_SCHEME['string2'])) + STYLES['comment'] = format(scheme.get('comment', + COLOR_SCHEME['comment']), 'italic') + STYLES['properObject'] = format(scheme.get('properObject', + COLOR_SCHEME['properObject']), 'italic') + STYLES['numbers'] = format(scheme.get('numbers', + COLOR_SCHEME['numbers'])) + STYLES['spaces'] = format(scheme.get('spaces', + COLOR_SCHEME['spaces'])) + STYLES['extras'] = format(scheme.get('extras', + COLOR_SCHEME['extras'])) + + +class Highlighter (QSyntaxHighlighter): + + # braces + braces = ['\\(', '\\)', '\\{', '\\}', '\\[', '\\]'] + + def __init__(self, document, lang, scheme=None): + QSyntaxHighlighter.__init__(self, document) + self.apply_highlight(lang, scheme) + + def apply_highlight(self, lang, scheme=None): + load_syntax() + langSyntax = SYNTAX.get(lang, {}) + if scheme: + restyle(scheme) + + keywords = langSyntax.get('keywords', []) + operators = langSyntax.get('operators', []) + extras = langSyntax.get('extras', []) + + rules = [] + + # Keyword, operator, brace and extras rules + rules += [(r'\b%s\b' % w, 0, STYLES['keyword']) + for w in keywords] + rules += [(r'%s' % o, 0, STYLES['operator']) + for o in operators] + rules += [(r'%s' % b, 0, STYLES['brace']) + for b in Highlighter.braces] + rules += [(r'\b%s\b' % e, 0, STYLES['extras']) + for e in extras] + + # All other rules + proper = langSyntax.get('properObject', None) + if proper is not None: + proper = '\\b' + str(proper[0]) + '\\b' + rules += [(proper, 0, STYLES['properObject'])] + + rules.append((r'__\w+__', 0, STYLES['properObject'])) + + definition = langSyntax.get('definition', []) + for de in definition: + expr = '\\b' + de + '\\b\\s*(\\w+)' + rules.append((expr, 1, STYLES['definition'])) + + # Numeric literals + rules += [ + (r'\b[+-]?[0-9]+[lL]?\b', 0, STYLES['numbers']), + (r'\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b', 0, STYLES['numbers']), + (r'\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b', 0, + STYLES['numbers']), + ] + + regex = langSyntax.get('regex', []) + for reg in regex: + expr = reg[0] + color = COLOR_SCHEME['extras'] + style = '' + if len(reg) > 1: + color = COLOR_SCHEME[reg[1]] + if len(reg) > 2: + style = reg[2] + rules.append((expr, 0, format(color, style))) + + comments = langSyntax.get('comment', []) + for co in comments: + expr = co + '[^\\n]*' + rules.append((expr, 0, STYLES['comment'])) + + stringChar = langSyntax.get('string', []) + for sc in stringChar: + expr = r'"[^"\\]*(\\.[^"\\]*)*"' if sc == '"' \ + else r"'[^'\\]*(\\.[^'\\]*)*'" + rules.append((expr, 0, STYLES['string'])) + + # Multi-line strings (expression, flag, style) + # FIXME: The triple-quotes in these two lines will mess up the + # syntax highlighting from this point onward + self.tri_single = (QRegExp("'''"), 1, STYLES['string2']) # ''' + self.tri_double = (QRegExp('"""'), 2, STYLES['string2']) # """ + + multi = langSyntax.get('multiline_comment', []) + if multi: + self.multi_start = (QRegExp(multi['open']), STYLES['comment']) + self.multi_end = (QRegExp(multi['close']), STYLES['comment']) + else: + self.multi_start = None + + # Build a QRegExp for each pattern + self.rules = [(QRegExp(pat), index, fmt) + for (pat, index, fmt) in rules] + #Apply Highlight to the document... (when colors change) + self.rehighlight() + + def highlightBlock(self, text): + """Apply syntax highlighting to the given block of text.""" + for expression, nth, format in self.rules: + index = expression.indexIn(text, 0) + + while index >= 0: + # We actually want the index of the nth match + index = expression.pos(nth) + length = expression.cap(nth).length() + self.setFormat(index, length, format) + index = expression.indexIn(text, index + length) + + self.setCurrentBlockState(0) + if not self.multi_start: + # Do multi-line strings + in_multiline = self.match_multiline(text, *self.tri_single) + if not in_multiline: + in_multiline = self.match_multiline(text, *self.tri_double) + else: + # Do multi-line comment + self.comment_multiline(text, self.multi_end[0], *self.multi_start) + + #Spaces + expression = QRegExp('\s+') + index = expression.indexIn(text, 0) + while index >= 0: + index = expression.pos(0) + length = expression.cap(0).length() + self.setFormat(index, length, STYLES['spaces']) + index = expression.indexIn(text, index + length) + + def match_multiline(self, text, delimiter, in_state, style): + """Do highlighting of multi-line strings. ``delimiter`` should be a + ``QRegExp`` for triple-single-quotes or triple-double-quotes, and + ``in_state`` should be a unique integer to represent the corresponding + state changes when inside those strings. Returns True if we're still + inside a multi-line string when this function is finished. + """ + # If inside triple-single quotes, start at 0 + if self.previousBlockState() == in_state: + start = 0 + add = 0 + # Otherwise, look for the delimiter on this line + else: + start = delimiter.indexIn(text) + # Move past this match + add = delimiter.matchedLength() + + # As long as there's a delimiter match on this line... + while start >= 0: + # Look for the ending delimiter + end = delimiter.indexIn(text, start + add) + # Ending delimiter on this line? + if end >= add: + length = end - start + add + delimiter.matchedLength() + self.setCurrentBlockState(0) + # No; multi-line string + else: + self.setCurrentBlockState(in_state) + length = text.length() - start + add + # Apply formatting + self.setFormat(start, length, style) + # Look for the next match + start = delimiter.indexIn(text, start + length) + + # Return True if still inside a multi-line string, False otherwise + if self.currentBlockState() == in_state: + return True + else: + return False + + def comment_multiline(self, text, delimiter_end, delimiter_start, style): + startIndex = 0 + if self.previousBlockState() != 1: + startIndex = delimiter_start.indexIn(text) + while startIndex >= 0: + endIndex = delimiter_end.indexIn(text, startIndex) + commentLength = 0 + if endIndex == -1: + self.setCurrentBlockState(1) + commentLength = text.length() - startIndex + else: + commentLength = endIndex - startIndex + \ + delimiter_end.matchedLength() + + self.setFormat(startIndex, commentLength, style) + startIndex = delimiter_start.indexIn(text, + startIndex + commentLength) + + +class EmptyHighlighter (QSyntaxHighlighter): + + def __init__(self, document): + QSyntaxHighlighter.__init__(self, document) + + def highlightBlock(self, text): + pass diff --git a/pilas/console/lang/python.json b/pilas/console/lang/python.json new file mode 100644 index 0000000..0c4f1b3 --- /dev/null +++ b/pilas/console/lang/python.json @@ -0,0 +1,81 @@ +{ + "comment": [ + "#" + ], + "extension": [ + "py" + ], + "definition": [ + "def", + "class" + ], + "string": [ + "'", + "\"" + ], + "properObject": [ + "self" + ], + "operators": [ + "=", + "==", + "!=", + "<", + "<=", + ">", + ">=", + "\\+", + "-", + "\\*", + "/", + "//", + "\\%", + "\\*\\*", + "\\+=", + "-=", + "\\*=", + "/=", + "\\%=", + "\\^", + "\\|", + "\\&", + "\\~", + ">>", + "<<" + ], + "keywords": [ + "and", + "assert", + "break", + "class", + "continue", + "def", + "del", + "elif", + "else", + "except", + "exec", + "finally", + "for", + "from", + "global", + "if", + "import", + "in", + "is", + "lambda", + "not", + "or", + "pass", + "print", + "raise", + "return", + "super", + "try", + "while", + "yield", + "None", + "True", + "False" + ] +} diff --git a/pilas/control.py b/pilas/control.py new file mode 100644 index 0000000..d91fbfd --- /dev/null +++ b/pilas/control.py @@ -0,0 +1,112 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +import pilas +from pilas.simbolos import * +import eventos + +__doc__ = """ +Módulo pilas.control +==================== + +""" + +class Control(object): + """Representa un control de teclado sencillo. + + Este objeto permite acceder al estado del teclado usando + atributos. + + Por ejemplo, con este objeto, para saber si el usuario + está pulsando el direccional hacia la izquierda de + puedes ejecutar:: + + if pilas.mundo.control.izquierda: + print 'Ha pulsado hacia la izquierda' + + Es decir, si bien Control es una clase, no hace falta + instanciarla. Ya existe un objeto que se puede consultar + bajo el nombre ``pilas.mundo.control``. + + Entonces, una vez que tienes la referencia para consultar, los + atributos que tiene este objeto control son:: + + izquierda + derecha + arriba + abajo + boton + + Cada uno de estos atributos te pueden devolver True, o False, indicando + si el control está pulsado o no. + + Ten en cuenta que este objeto también se puede imprimir usando + la sentencia ``print``. Esto es útil para ver el estado completo + del control de una sola vez: + + >>> print pilas.mundo.control + + + + Consultando controles desde un actor: + + Una forma habitual de usar los controles, es consultarlos + directamente desde el codigo de un actor. + + Para consultar los controles para cambiar la posicion horizontal de + un actor podrías implementar el método ``actualizar``:: + + class Patito(pilas.actores.Actor): + + def __init__(self): + pilas.actores.Actor.__init__(self) + self.imagen = "patito.png" + + def actualizar(self): + if pilas.mundo.control.izquierda: + self.x -= 5 + self.espejado = True + elif pilas.mundo.control.derecha: + self.x += 5 + self.espejado = False + + .. image:: ../../pilas/data/patito.png + """ + + def __init__(self): + self.izquierda = False + self.derecha = False + self.arriba = False + self.abajo = False + self.boton = False + + eventos.pulsa_tecla.conectar(self.cuando_pulsa_una_tecla) + eventos.suelta_tecla.conectar(self.cuando_suelta_una_tecla) + + def cuando_pulsa_una_tecla(self, evento): + self.procesar_cambio_de_estado_en_la_tecla(evento.codigo, True) + + def cuando_suelta_una_tecla(self, evento): + self.procesar_cambio_de_estado_en_la_tecla(evento.codigo, False) + + def procesar_cambio_de_estado_en_la_tecla(self, codigo, estado): + mapa = { + IZQUIERDA: 'izquierda', + DERECHA: 'derecha', + ARRIBA: 'arriba', + ABAJO: 'abajo', + SELECCION: 'boton', + } + + if mapa.has_key(codigo): + setattr(self, mapa[codigo], estado) + + def __str__(self): + return "" %( + str(self.izquierda), str(self.derecha), str(self.arriba), + str(self.abajo), str(self.boton)) diff --git a/pilas/data/aceituna.png b/pilas/data/aceituna.png new file mode 100644 index 0000000..dae99e4 --- /dev/null +++ b/pilas/data/aceituna.png Binary files differ diff --git a/pilas/data/aceituna_burla.png b/pilas/data/aceituna_burla.png new file mode 100644 index 0000000..3d3a98e --- /dev/null +++ b/pilas/data/aceituna_burla.png Binary files differ diff --git a/pilas/data/aceituna_grita.png b/pilas/data/aceituna_grita.png new file mode 100644 index 0000000..34b8905 --- /dev/null +++ b/pilas/data/aceituna_grita.png Binary files differ diff --git a/pilas/data/aceituna_risa.png b/pilas/data/aceituna_risa.png new file mode 100644 index 0000000..8ae5a88 --- /dev/null +++ b/pilas/data/aceituna_risa.png Binary files differ diff --git a/pilas/data/banana.png b/pilas/data/banana.png new file mode 100644 index 0000000..b352b03 --- /dev/null +++ b/pilas/data/banana.png Binary files differ diff --git a/pilas/data/batalhao.png b/pilas/data/batalhao.png new file mode 100644 index 0000000..9734c81 --- /dev/null +++ b/pilas/data/batalhao.png Binary files differ diff --git a/pilas/data/bomba.png b/pilas/data/bomba.png new file mode 100644 index 0000000..9cf957a --- /dev/null +++ b/pilas/data/bomba.png Binary files differ diff --git a/pilas/data/boton/boton_normal.png b/pilas/data/boton/boton_normal.png new file mode 100644 index 0000000..706d180 --- /dev/null +++ b/pilas/data/boton/boton_normal.png Binary files differ diff --git a/pilas/data/boton/boton_over.png b/pilas/data/boton/boton_over.png new file mode 100644 index 0000000..b5848cd --- /dev/null +++ b/pilas/data/boton/boton_over.png Binary files differ diff --git a/pilas/data/boton/boton_press.png b/pilas/data/boton/boton_press.png new file mode 100644 index 0000000..436871d --- /dev/null +++ b/pilas/data/boton/boton_press.png Binary files differ diff --git a/pilas/data/boton/tema.png b/pilas/data/boton/tema.png new file mode 100644 index 0000000..8ce9420 --- /dev/null +++ b/pilas/data/boton/tema.png Binary files differ diff --git a/pilas/data/boton/tema.xcf b/pilas/data/boton/tema.xcf new file mode 100644 index 0000000..3dcc2ba --- /dev/null +++ b/pilas/data/boton/tema.xcf Binary files differ diff --git a/pilas/data/caja.png b/pilas/data/caja.png new file mode 100644 index 0000000..3f8a151 --- /dev/null +++ b/pilas/data/caja.png Binary files differ diff --git a/pilas/data/cooperativista/alerta.png b/pilas/data/cooperativista/alerta.png new file mode 100644 index 0000000..17f103f --- /dev/null +++ b/pilas/data/cooperativista/alerta.png Binary files differ diff --git a/pilas/data/cooperativista/camina.png b/pilas/data/cooperativista/camina.png new file mode 100644 index 0000000..dbdb5a5 --- /dev/null +++ b/pilas/data/cooperativista/camina.png Binary files differ diff --git a/pilas/data/cooperativista/camina_sujeta.png b/pilas/data/cooperativista/camina_sujeta.png new file mode 100644 index 0000000..f4235bb --- /dev/null +++ b/pilas/data/cooperativista/camina_sujeta.png Binary files differ diff --git a/pilas/data/cooperativista/ok.png b/pilas/data/cooperativista/ok.png new file mode 100644 index 0000000..7d86a64 --- /dev/null +++ b/pilas/data/cooperativista/ok.png Binary files differ diff --git a/pilas/data/cooperativista/parado.png b/pilas/data/cooperativista/parado.png new file mode 100644 index 0000000..91f0266 --- /dev/null +++ b/pilas/data/cooperativista/parado.png Binary files differ diff --git a/pilas/data/cooperativista/parado_sujeta.png b/pilas/data/cooperativista/parado_sujeta.png new file mode 100644 index 0000000..fb6fd68 --- /dev/null +++ b/pilas/data/cooperativista/parado_sujeta.png Binary files differ diff --git a/pilas/data/cooperativista/trabajando.png b/pilas/data/cooperativista/trabajando.png new file mode 100644 index 0000000..47e59b4 --- /dev/null +++ b/pilas/data/cooperativista/trabajando.png Binary files differ diff --git a/pilas/data/cursordisparo.png b/pilas/data/cursordisparo.png new file mode 100644 index 0000000..21daaa3 --- /dev/null +++ b/pilas/data/cursordisparo.png Binary files differ diff --git a/pilas/data/cursores/arrastrando.png b/pilas/data/cursores/arrastrando.png new file mode 100644 index 0000000..3b56e22 --- /dev/null +++ b/pilas/data/cursores/arrastrando.png Binary files differ diff --git a/pilas/data/cursores/dibujar.png b/pilas/data/cursores/dibujar.png new file mode 100644 index 0000000..bd90f74 --- /dev/null +++ b/pilas/data/cursores/dibujar.png Binary files differ diff --git a/pilas/data/cursores/normal.png b/pilas/data/cursores/normal.png new file mode 100644 index 0000000..7feffb0 --- /dev/null +++ b/pilas/data/cursores/normal.png Binary files differ diff --git a/pilas/data/cursores/prohibido.png b/pilas/data/cursores/prohibido.png new file mode 100644 index 0000000..77010c2 --- /dev/null +++ b/pilas/data/cursores/prohibido.png Binary files differ diff --git a/pilas/data/disparo.png b/pilas/data/disparo.png new file mode 100644 index 0000000..266540d --- /dev/null +++ b/pilas/data/disparo.png Binary files differ diff --git a/pilas/data/ejes.png b/pilas/data/ejes.png new file mode 100644 index 0000000..74f14b6 --- /dev/null +++ b/pilas/data/ejes.png Binary files differ diff --git a/pilas/data/ejes.svg b/pilas/data/ejes.svg new file mode 100644 index 0000000..39b2dda --- /dev/null +++ b/pilas/data/ejes.svg @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + eje X + eje Y + + diff --git a/pilas/data/ejes.xcf b/pilas/data/ejes.xcf new file mode 100644 index 0000000..8b1d175 --- /dev/null +++ b/pilas/data/ejes.xcf Binary files differ diff --git a/pilas/data/estrella.png b/pilas/data/estrella.png new file mode 100644 index 0000000..223462a --- /dev/null +++ b/pilas/data/estrella.png Binary files differ diff --git a/pilas/data/explosion.png b/pilas/data/explosion.png new file mode 100644 index 0000000..d2e7fa3 --- /dev/null +++ b/pilas/data/explosion.png Binary files differ diff --git a/pilas/data/explosion.wav b/pilas/data/explosion.wav new file mode 100644 index 0000000..c8acedf --- /dev/null +++ b/pilas/data/explosion.wav Binary files differ diff --git a/pilas/data/fondos/blanco.png b/pilas/data/fondos/blanco.png new file mode 100644 index 0000000..281b88a --- /dev/null +++ b/pilas/data/fondos/blanco.png Binary files differ diff --git a/pilas/data/fondos/espacio.jpg b/pilas/data/fondos/espacio.jpg new file mode 100644 index 0000000..60981a3 --- /dev/null +++ b/pilas/data/fondos/espacio.jpg Binary files differ diff --git a/pilas/data/fondos/noche.jpg b/pilas/data/fondos/noche.jpg new file mode 100644 index 0000000..7e93b7d --- /dev/null +++ b/pilas/data/fondos/noche.jpg Binary files differ diff --git a/pilas/data/fondos/pasto.png b/pilas/data/fondos/pasto.png new file mode 100644 index 0000000..107054a --- /dev/null +++ b/pilas/data/fondos/pasto.png Binary files differ diff --git a/pilas/data/fondos/selva.jpg b/pilas/data/fondos/selva.jpg new file mode 100644 index 0000000..b6eff87 --- /dev/null +++ b/pilas/data/fondos/selva.jpg Binary files differ diff --git a/pilas/data/fondos/tarde.jpg b/pilas/data/fondos/tarde.jpg new file mode 100644 index 0000000..cc1bc8b --- /dev/null +++ b/pilas/data/fondos/tarde.jpg Binary files differ diff --git a/pilas/data/fondos/volley.jpg b/pilas/data/fondos/volley.jpg new file mode 100644 index 0000000..b75b3c6 --- /dev/null +++ b/pilas/data/fondos/volley.jpg Binary files differ diff --git a/pilas/data/globo.png b/pilas/data/globo.png new file mode 100644 index 0000000..5127438 --- /dev/null +++ b/pilas/data/globo.png Binary files differ diff --git a/pilas/data/globo.xcf b/pilas/data/globo.xcf new file mode 100644 index 0000000..bb379b4 --- /dev/null +++ b/pilas/data/globo.xcf Binary files differ diff --git a/pilas/data/grillas/plataformas_10_10.png b/pilas/data/grillas/plataformas_10_10.png new file mode 100644 index 0000000..a796b3a --- /dev/null +++ b/pilas/data/grillas/plataformas_10_10.png Binary files differ diff --git a/pilas/data/icono_pausa.png b/pilas/data/icono_pausa.png new file mode 100644 index 0000000..cc3a371 --- /dev/null +++ b/pilas/data/icono_pausa.png Binary files differ diff --git a/pilas/data/iconos/borrar.png b/pilas/data/iconos/borrar.png new file mode 100644 index 0000000..2f59c5e --- /dev/null +++ b/pilas/data/iconos/borrar.png Binary files differ diff --git a/pilas/data/iconos/lupa.png b/pilas/data/iconos/lupa.png new file mode 100644 index 0000000..0a3bf9c --- /dev/null +++ b/pilas/data/iconos/lupa.png Binary files differ diff --git a/pilas/data/iconos/ok.png b/pilas/data/iconos/ok.png new file mode 100644 index 0000000..fec1c0a --- /dev/null +++ b/pilas/data/iconos/ok.png Binary files differ diff --git a/pilas/data/interfaz/barra.png b/pilas/data/interfaz/barra.png new file mode 100644 index 0000000..cd0bb8f --- /dev/null +++ b/pilas/data/interfaz/barra.png Binary files differ diff --git a/pilas/data/interfaz/caja.png b/pilas/data/interfaz/caja.png new file mode 100644 index 0000000..4992a13 --- /dev/null +++ b/pilas/data/interfaz/caja.png Binary files differ diff --git a/pilas/data/interfaz/deslizador.png b/pilas/data/interfaz/deslizador.png new file mode 100644 index 0000000..e59e3d2 --- /dev/null +++ b/pilas/data/interfaz/deslizador.png Binary files differ diff --git a/pilas/data/interfaz/selector.png b/pilas/data/interfaz/selector.png new file mode 100644 index 0000000..daeb883 --- /dev/null +++ b/pilas/data/interfaz/selector.png Binary files differ diff --git a/pilas/data/interfaz/selector_seleccionado.png b/pilas/data/interfaz/selector_seleccionado.png new file mode 100644 index 0000000..5db41ea --- /dev/null +++ b/pilas/data/interfaz/selector_seleccionado.png Binary files differ diff --git a/pilas/data/invisible.png b/pilas/data/invisible.png new file mode 100644 index 0000000..79cc8ed --- /dev/null +++ b/pilas/data/invisible.png Binary files differ diff --git a/pilas/data/juegobase/data/logo.png b/pilas/data/juegobase/data/logo.png new file mode 100644 index 0000000..45fc1f0 --- /dev/null +++ b/pilas/data/juegobase/data/logo.png Binary files differ diff --git a/pilas/data/juegobase/ejecutar.py b/pilas/data/juegobase/ejecutar.py new file mode 100644 index 0000000..b889021 --- /dev/null +++ b/pilas/data/juegobase/ejecutar.py @@ -0,0 +1,9 @@ +import pilas + +pilas.iniciar() +pilas.fondos.Pasto() +pilas.avisar('Use Alt+q para salir.') + +actor = pilas.actores.Actor("data/logo.png") + +pilas.ejecutar() diff --git a/pilas/data/mapa.png b/pilas/data/mapa.png new file mode 100644 index 0000000..3fde3f0 --- /dev/null +++ b/pilas/data/mapa.png Binary files differ diff --git a/pilas/data/marcianitos/martian.png b/pilas/data/marcianitos/martian.png new file mode 100644 index 0000000..b89c14a --- /dev/null +++ b/pilas/data/marcianitos/martian.png Binary files differ diff --git a/pilas/data/marcianitos/martian.xcf b/pilas/data/marcianitos/martian.xcf new file mode 100644 index 0000000..3b3faf9 --- /dev/null +++ b/pilas/data/marcianitos/martian.xcf Binary files differ diff --git a/pilas/data/marcianitos/martian_64_128.xcf b/pilas/data/marcianitos/martian_64_128.xcf new file mode 100644 index 0000000..b9c8f7f --- /dev/null +++ b/pilas/data/marcianitos/martian_64_128.xcf Binary files differ diff --git a/pilas/data/marcianitos/martian_64_129.xcf b/pilas/data/marcianitos/martian_64_129.xcf new file mode 100644 index 0000000..b9c8f7f --- /dev/null +++ b/pilas/data/marcianitos/martian_64_129.xcf Binary files differ diff --git a/pilas/data/moneda.png b/pilas/data/moneda.png new file mode 100644 index 0000000..a2b8bbc --- /dev/null +++ b/pilas/data/moneda.png Binary files differ diff --git a/pilas/data/monkey_normal.png b/pilas/data/monkey_normal.png new file mode 100644 index 0000000..e11f0e9 --- /dev/null +++ b/pilas/data/monkey_normal.png Binary files differ diff --git a/pilas/data/monkey_shout.png b/pilas/data/monkey_shout.png new file mode 100644 index 0000000..7bbc223 --- /dev/null +++ b/pilas/data/monkey_shout.png Binary files differ diff --git a/pilas/data/monkey_smile.png b/pilas/data/monkey_smile.png new file mode 100644 index 0000000..63121e0 --- /dev/null +++ b/pilas/data/monkey_smile.png Binary files differ diff --git a/pilas/data/mono.png b/pilas/data/mono.png new file mode 100644 index 0000000..e11f0e9 --- /dev/null +++ b/pilas/data/mono.png Binary files differ diff --git a/pilas/data/nave.png b/pilas/data/nave.png new file mode 100644 index 0000000..73fd3ac --- /dev/null +++ b/pilas/data/nave.png Binary files differ diff --git a/pilas/data/parque.tmx b/pilas/data/parque.tmx new file mode 100644 index 0000000..c95ca19 --- /dev/null +++ b/pilas/data/parque.tmx @@ -0,0 +1,44 @@ + + + + + + + +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,34,1,1,1,1,1,1,1,1,2,1,1,2, +1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,34,1,1,1,1,1,1,1,2,1,1,34,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +35,35,35,35,35,35,35,35,35,35,35,35,35,35,35,35,35,35,35,35, +1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,34,1,1,1,1, +1,1,2,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,19,3,3,4,20,0,0,0,0, +0,0,0,0,33,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,20,0,0,0,0,0,17,0,0,0,0,0,0,33,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +33,0,0,0,0,33,0,0,0,0,0,0,33,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,33,0,0,0,0,0,0,0,0,33,0,0,0,0,0,33,0,0, +0,0,0,0,0,0,33,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,33,0,0,0,0,0,0,0,19,3,4,3,20,0,0,0,33,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + diff --git a/pilas/data/patito.png b/pilas/data/patito.png new file mode 100644 index 0000000..6be4566 --- /dev/null +++ b/pilas/data/patito.png Binary files differ diff --git a/pilas/data/patito_desde_arriba.png b/pilas/data/patito_desde_arriba.png new file mode 100644 index 0000000..3dee5d6 --- /dev/null +++ b/pilas/data/patito_desde_arriba.png Binary files differ diff --git a/pilas/data/pelota.png b/pilas/data/pelota.png new file mode 100644 index 0000000..6a8afc2 --- /dev/null +++ b/pilas/data/pelota.png Binary files differ diff --git a/pilas/data/piedra_chica.png b/pilas/data/piedra_chica.png new file mode 100644 index 0000000..82ed70c --- /dev/null +++ b/pilas/data/piedra_chica.png Binary files differ diff --git a/pilas/data/piedra_grande.png b/pilas/data/piedra_grande.png new file mode 100644 index 0000000..3395d26 --- /dev/null +++ b/pilas/data/piedra_grande.png Binary files differ diff --git a/pilas/data/piedra_media.png b/pilas/data/piedra_media.png new file mode 100644 index 0000000..0db6de5 --- /dev/null +++ b/pilas/data/piedra_media.png Binary files differ diff --git a/pilas/data/pilas-logo.png b/pilas/data/pilas-logo.png new file mode 100644 index 0000000..f9c9377 --- /dev/null +++ b/pilas/data/pilas-logo.png Binary files differ diff --git a/pilas/data/pingu.png b/pilas/data/pingu.png new file mode 100644 index 0000000..c013604 --- /dev/null +++ b/pilas/data/pingu.png Binary files differ diff --git a/pilas/data/plataformas.png b/pilas/data/plataformas.png new file mode 100644 index 0000000..a796b3a --- /dev/null +++ b/pilas/data/plataformas.png Binary files differ diff --git a/pilas/data/prueba.png b/pilas/data/prueba.png new file mode 100644 index 0000000..bd24c5c --- /dev/null +++ b/pilas/data/prueba.png Binary files differ diff --git a/pilas/data/punto.png b/pilas/data/punto.png new file mode 100644 index 0000000..577d76b --- /dev/null +++ b/pilas/data/punto.png Binary files differ diff --git a/pilas/data/shout.wav b/pilas/data/shout.wav new file mode 100644 index 0000000..4a465c3 --- /dev/null +++ b/pilas/data/shout.wav Binary files differ diff --git a/pilas/data/sin_imagen.png b/pilas/data/sin_imagen.png new file mode 100644 index 0000000..cd98643 --- /dev/null +++ b/pilas/data/sin_imagen.png Binary files differ diff --git a/pilas/data/smile.wav b/pilas/data/smile.wav new file mode 100644 index 0000000..7c14f8c --- /dev/null +++ b/pilas/data/smile.wav Binary files differ diff --git a/pilas/data/tick.wav b/pilas/data/tick.wav new file mode 100644 index 0000000..3d4945a --- /dev/null +++ b/pilas/data/tick.wav Binary files differ diff --git a/pilas/data/tortuga.png b/pilas/data/tortuga.png new file mode 100644 index 0000000..95b325b --- /dev/null +++ b/pilas/data/tortuga.png Binary files differ diff --git a/pilas/data/window.ui b/pilas/data/window.ui new file mode 100644 index 0000000..64df717 --- /dev/null +++ b/pilas/data/window.ui @@ -0,0 +1,34 @@ + + + Window + + + + 0 + 0 + 503 + 477 + + + + Form + + + + + + + + 0 + 0 + 0 + + + + + + + + + + diff --git a/pilas/depurador.py b/pilas/depurador.py new file mode 100644 index 0000000..9a662f9 --- /dev/null +++ b/pilas/depurador.py @@ -0,0 +1,202 @@ +# -*- encoding: utf-8 -*- +# pilas engine - a video game framework. +# +# copyright 2010 - hugo ruscitti +# license: lgplv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# website - http://www.pilas-engine.com.ar + +import pilas +from pilas import pilasversion +import sys + +class Depurador(object): + """Esta clase permite hacer depuraciones visuales. + + La depuracion visual en pilas consiste en poder mostrar informacion + que generalmente es invisible a los jugadores. Por ejemplo, donde + estan situados los puntos de control, los radios de colision etc. + + Esta clase administra varios modos depuracion, que son los + que dibujan figuras geometricas. + """ + + def __init__(self, lienzo, fps): + self.modos = [] + self.lienzo = lienzo + ModoDepurador.grosor_de_lineas = 1 + self.fps = fps + self.posicion_del_mouse = (0, 0) + pilas.eventos.mueve_mouse.conectar(self.cuando_mueve_el_mouse) + pilas.eventos.pulsa_tecla.conectar(self.cuando_pulsa_tecla) + + def cuando_mueve_el_mouse(self, evento): + self.posicion_del_mouse = (evento.x, evento.y) + return True + + def comienza_dibujado(self, motor): + for m in self.modos: + m.comienza_dibujado(motor, self.lienzo) + + def dibuja_al_actor(self, motor, actor): + for m in self.modos: + m.dibuja_al_actor(motor, self.lienzo, actor) + + def termina_dibujado(self, motor): + if self.modos: + self._mostrar_cantidad_de_actores(motor) + self._mostrar_cuadros_por_segundo(motor) + self._mostrar_posicion_del_mouse(motor) + self._mostrar_nombres_de_modos(motor) + + for m in self.modos: + m.termina_dibujado(motor, self.lienzo) + + def cuando_pulsa_tecla(self, evento): + if evento.codigo == 'F7': + self._alternar_modo(ModoInformacionDeSistema) + elif evento.codigo == 'F8': + self._alternar_modo(ModoPuntosDeControl) + elif evento.codigo == 'F9': + self._alternar_modo(ModoRadiosDeColision) + elif evento.codigo == 'F10': + self._alternar_modo(ModoArea) + elif evento.codigo == 'F11': + self._alternar_modo(ModoFisica) + elif evento.codigo == 'F12': + self._alternar_modo(ModoPosicion) + elif evento.texto == '+': + self._cambiar_grosor_de_bordes(+1) + elif evento.texto == '-': + self._cambiar_grosor_de_bordes(-1) + + def _cambiar_grosor_de_bordes(self, cambio): + ModoDepurador.grosor_de_lineas = max(1, ModoDepurador.grosor_de_lineas + cambio) + + def _alternar_modo(self, clase_del_modo): + clases_activas = [x.__class__ for x in self.modos] + + if clase_del_modo in clases_activas: + self._desactivar_modo(clase_del_modo) + else: + self._activar_modo(clase_del_modo) + + def _activar_modo(self, clase_del_modo): + pilas.eventos.inicia_modo_depuracion.send('depurador') + instancia_del_modo = clase_del_modo(self) + self.modos.append(instancia_del_modo) + # Ordena todos los registros por numero de tecla. + self.modos.sort(key=lambda x: x.orden_de_tecla()) + + def _desactivar_modo(self, clase_del_modo): + instancia_a_eliminar = [x for x in self.modos + if x.__class__ == clase_del_modo] + self.modos.remove(instancia_a_eliminar[0]) + + if not self.modos: + pilas.eventos.sale_modo_depuracion.send('depurador') + + def _mostrar_nombres_de_modos(self, motor): + dy = 0 + izquierda, derecha, arriba, abajo = pilas.utils.obtener_bordes() + + for modo in self.modos: + texto = modo.tecla + " " + modo.__class__.__name__ + " habilitado." + self.lienzo.texto_absoluto(motor, texto, izquierda + 10, arriba -20 +dy, + color=pilas.colores.violeta) + dy -= 20 + + def _mostrar_posicion_del_mouse(self, motor): + izquierda, derecha, arriba, abajo = pilas.utils.obtener_bordes() + x, y = self.posicion_del_mouse + texto = u"Posición del mouse: x=%d y=%d " %(x, y) + self.lienzo.texto_absoluto(motor, texto, derecha - 230, abajo + 10, color=pilas.colores.violeta) + + def _mostrar_cuadros_por_segundo(self, motor): + izquierda, derecha, arriba, abajo = pilas.utils.obtener_bordes() + rendimiento = self.fps.obtener_cuadros_por_segundo() + texto = "Cuadros por segundo: %s" %(rendimiento) + self.lienzo.texto_absoluto(motor, texto, izquierda + 10, abajo + 10, + color=pilas.colores.violeta) + + def _mostrar_cantidad_de_actores(self, motor): + izquierda, derecha, arriba, abajo = pilas.utils.obtener_bordes() + total_de_actores = len(pilas.actores.todos) + texto = "Cantidad de actores: %s" %(total_de_actores) + self.lienzo.texto_absoluto(motor, texto, izquierda + 10, abajo + 30, + color=pilas.colores.violeta) + +class ModoDepurador(object): + tecla = "F00" + + def __init__(self, depurador): + self.depurador = depurador + + def comienza_dibujado(self, motor, lienzo): + pass + + def dibuja_al_actor(self, motor, lienzo, actor): + pass + + def termina_dibujado(self, motor, lienzo): + pass + + def orden_de_tecla(self): + return int(self.tecla[1:]) + +class ModoPuntosDeControl(ModoDepurador): + tecla = "F8" + + def dibuja_al_actor(self, motor, lienzo, actor): + lienzo.cruz(motor, actor.x, actor.y, color=pilas.colores.rojo, grosor=ModoDepurador.grosor_de_lineas) + +class ModoRadiosDeColision(ModoDepurador): + tecla = "F9" + + def dibuja_al_actor(self, motor, lienzo, actor): + lienzo.circulo(motor, actor.x, actor.y, actor.radio_de_colision, color=pilas.colores.verde, grosor=ModoDepurador.grosor_de_lineas) + +class ModoArea(ModoDepurador): + tecla = "F10" + + def dibuja_al_actor(self, motor, lienzo, actor): + dx, dy = actor.centro + lienzo.rectangulo(motor, actor.x - dx, actor.y + dy, actor.ancho, actor.alto, color=pilas.colores.azul, grosor=ModoDepurador.grosor_de_lineas) + +class ModoPosicion(ModoDepurador): + tecla = "F12" + + def __init__(self, depurador): + ModoDepurador.__init__(self, depurador) + + def dibuja_al_actor(self, motor, lienzo, actor): + if not isinstance(actor, pilas.fondos.Fondo): + texto = "(%d, %d)" %(actor.x, actor.y) + lienzo.texto(motor, texto, actor.derecha, actor.abajo, color=pilas.colores.violeta) + +class ModoFisica(ModoDepurador): + tecla = "F11" + + def termina_dibujado(self, motor, lienzo): + grosor = ModoDepurador.grosor_de_lineas + pilas.mundo.fisica.dibujar_figuras_sobre_lienzo(motor, lienzo, grosor) + +class ModoInformacionDeSistema(ModoDepurador): + tecla = "F7" + + def __init__(self, depurador): + ModoDepurador.__init__(self, depurador) + + self.informacion = [ + "Usando el motor: " + pilas.mundo.motor.__class__.__name__, + "Sistema: " + sys.platform, + "Version de pilas: " + pilasversion.VERSION, + "Version de python: " + sys.subversion[0] + " " + sys.subversion[1], + ] + + def termina_dibujado(self, motor, lienzo): + izquierda, derecha, arriba, abajo = pilas.utils.obtener_bordes() + + for (i, texto) in enumerate(self.informacion): + posicion_y = abajo + 50 + i * 20 + lienzo.texto(motor, texto, izquierda + 10, posicion_y, color=pilas.colores.negro) diff --git a/pilas/dispatch/__init__.py b/pilas/dispatch/__init__.py new file mode 100644 index 0000000..683aba9 --- /dev/null +++ b/pilas/dispatch/__init__.py @@ -0,0 +1,9 @@ +"""Multi-consumer multi-producer dispatching mechanism + +Originally based on pydispatch (BSD) http://pypi.python.org/pypi/PyDispatcher/2.0.1 +See license.txt for original license. + +Heavily modified for Django's purposes. +""" + +from pilas.dispatch.dispatcher import Signal diff --git a/pilas/dispatch/dispatcher.py b/pilas/dispatch/dispatcher.py new file mode 100644 index 0000000..5576625 --- /dev/null +++ b/pilas/dispatch/dispatcher.py @@ -0,0 +1,243 @@ +import weakref + +from pilas.dispatch import saferef + +WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref) + + +class DictObj(object): + def __init__(self, d): + self.d = d + + def __getattr__(self, m): + return self.d.get(m, None) + + def __str__(self): + return "" %(str(self.d)) + +def _make_id(target): + if hasattr(target, 'im_func'): + return (id(target.im_self), id(target.im_func)) + return id(target) + +class Signal(object): + """ + Base class for all signals + + Internal attributes: + + receivers + { receriverkey (id) : weakref(receiver) } + """ + + def __init__(self, providing_args=None): + """ + Create a new signal. + + providing_args + A list of the arguments this signal can pass along in a send() call. + """ + self.receivers = [] + if providing_args is None: + providing_args = [] + self.providing_args = set(providing_args) + + def conectar(self, receptor, emisor=None, weak=True, uid=None): + return self.connect(receptor, emisor, weak, uid) + + def connect(self, receiver, sender=None, weak=True, uid=None): + """ + Connect receiver to sender for signal. + + Arguments: + + receiver + A function or an instance method which is to receive signals. + Receivers must be hashable objects. + + if weak is True, then receiver must be weak-referencable (more + precisely saferef.safeRef() must be able to create a reference + to the receiver). + + Receivers must be able to accept keyword arguments. + + If receivers have a uid attribute, the receiver will + not be added if another receiver already exists with that + uid. + + sender + The sender to which the receiver should respond Must either be + of type Signal, or None to receive events from any sender. + + weak + Whether to use weak references to the receiver By default, the + module will attempt to use weak references to the receiver + objects. If this parameter is false, then strong references will + be used. + + uid + An identifier used to uniquely identify a particular instance of + a receiver. This will usually be a string, though it may be + anything hashable. + """ + + if uid: + lookup_key = (uid, _make_id(sender)) + else: + lookup_key = (_make_id(receiver), _make_id(sender)) + + if weak: + receiver = saferef.safeRef(receiver, onDelete=self._remove_receiver) + + for r_key, _ in self.receivers: + if r_key == lookup_key: + break + else: + self.receivers.append((lookup_key, receiver)) + + def desconectar(self, receptor=None, emisor=None, weak=True, uid=None): + self.disconnect(receptor, emisor, weak, uid) + + def disconnect(self, receiver=None, sender=None, weak=True, uid=None): + """ + Disconnect receiver from sender for signal. + + If weak references are used, disconnect need not be called. The receiver + will be remove from dispatch automatically. + + Arguments: + + receiver + The registered receiver to disconnect. May be none if + uid is specified. + + sender + The registered sender to disconnect + + weak + The weakref state to disconnect + + uid + the unique identifier of the receiver to disconnect + """ + if uid: + lookup_key = (uid, _make_id(sender)) + else: + lookup_key = (_make_id(receiver), _make_id(sender)) + + for index in xrange(len(self.receivers)): + (r_key, _) = self.receivers[index] + if r_key == lookup_key: + del self.receivers[index] + break + + def send(self, sender, **named): + """ + Send signal from sender to all connected receivers. + + If any receiver raises an error, the error propagates back through send, + terminating the dispatch loop, so it is quite possible to not have all + receivers called if a raises an error. + + Arguments: + + sender + The sender of the signal Either a specific object or None. + + named + Named arguments which will be passed to receivers. + + Returns a list of tuple pairs [(receiver, response), ... ]. + """ + responses = [] + if not self.receivers: + return responses + + for receiver in self._live_receivers(_make_id(sender)): + response = receiver(DictObj(named)) + responses.append((receiver, response)) + return responses + + def send_robust(self, sender, **named): + """ + Send signal from sender to all connected receivers catching errors. + + Arguments: + + sender + The sender of the signal Can be any python object (normally one + registered with a connect if you actually want something to + occur). + + named + Named arguments which will be passed to receivers. These + arguments must be a subset of the argument names defined in + providing_args. + + Return a list of tuple pairs [(receiver, response), ... ]. May raise + DispatcherKeyError. + + if any receiver raises an error (specifically any subclass of + Exception), the error instance is returned as the result for that + receiver. + """ + responses = [] + if not self.receivers: + return responses + + # Call each receiver with whatever arguments it can accept. + # Return a list of tuple pairs [(receiver, response), ... ]. + for receiver in self._live_receivers(_make_id(sender)): + try: + response = receiver(signal=self, sender=sender, **named) + except Exception, err: + responses.append((receiver, err)) + else: + responses.append((receiver, response)) + return responses + + def _live_receivers(self, senderkey): + """ + Filter sequence of receivers to get resolved, live receivers. + + This checks for weak references and resolves them, then returning only + live receivers. + """ + none_senderkey = _make_id(None) + receivers = [] + + for (receiverkey, r_senderkey), receiver in self.receivers: + if r_senderkey == none_senderkey or r_senderkey == senderkey: + if isinstance(receiver, WEAKREF_TYPES): + # Dereference the weak reference. + receiver = receiver() + if receiver is not None: + receivers.append(receiver) + else: + receivers.append(receiver) + return receivers + + def _remove_receiver(self, receiver): + """ + Remove dead receivers from connections. + """ + + to_remove = [] + for key, connected_receiver in self.receivers: + if connected_receiver == receiver: + to_remove.append(key) + for key in to_remove: + for idx, (r_key, _) in enumerate(self.receivers): + if r_key == key: + del self.receivers[idx] + + def esta_conectado(self): + "Indica si tiene alguna funcion conectada." + return self.receivers + + def imprimir_funciones_conectadas(self): + "Imprime todas las funciones que tiene conectado el evento." + for clave, referencia in self.receivers: + nombre = referencia.__str__() + nombre = nombre[nombre.index('(')+1:nombre.index(")")] + print "\t", nombre \ No newline at end of file diff --git a/pilas/dispatch/license.txt b/pilas/dispatch/license.txt new file mode 100644 index 0000000..505090d --- /dev/null +++ b/pilas/dispatch/license.txt @@ -0,0 +1,36 @@ +django.dispatch was originally forked from PyDispatcher. + +PyDispatcher License: + + Copyright (c) 2001-2003, Patrick K. O'Brien and Contributors + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + The name of Patrick K. O'Brien, or the name of any Contributor, + may not be used to endorse or promote products derived from this + software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/pilas/dispatch/saferef.py b/pilas/dispatch/saferef.py new file mode 100644 index 0000000..8bcfd8a --- /dev/null +++ b/pilas/dispatch/saferef.py @@ -0,0 +1,250 @@ +""" +"Safe weakrefs", originally from pyDispatcher. + +Provides a way to safely weakref any function, including bound methods (which +aren't handled by the core weakref module). +""" + +import weakref, traceback + +def safeRef(target, onDelete = None): + """Return a *safe* weak reference to a callable target + + target -- the object to be weakly referenced, if it's a + bound method reference, will create a BoundMethodWeakref, + otherwise creates a simple weakref. + onDelete -- if provided, will have a hard reference stored + to the callable to be called after the safe reference + goes out of scope with the reference object, (either a + weakref or a BoundMethodWeakref) as argument. + """ + if hasattr(target, 'im_self'): + if target.im_self is not None: + # Turn a bound method into a BoundMethodWeakref instance. + # Keep track of these instances for lookup by disconnect(). + assert hasattr(target, 'im_func'), """safeRef target %r has im_self, but no im_func, don't know how to create reference"""%( target,) + reference = get_bound_method_weakref( + target=target, + onDelete=onDelete + ) + return reference + if callable(onDelete): + return weakref.ref(target, onDelete) + else: + return weakref.ref( target ) + +class BoundMethodWeakref(object): + """'Safe' and reusable weak references to instance methods + + BoundMethodWeakref objects provide a mechanism for + referencing a bound method without requiring that the + method object itself (which is normally a transient + object) is kept alive. Instead, the BoundMethodWeakref + object keeps weak references to both the object and the + function which together define the instance method. + + Attributes: + key -- the identity key for the reference, calculated + by the class's calculateKey method applied to the + target instance method + deletionMethods -- sequence of callable objects taking + single argument, a reference to this object which + will be called when *either* the target object or + target function is garbage collected (i.e. when + this object becomes invalid). These are specified + as the onDelete parameters of safeRef calls. + weakSelf -- weak reference to the target object + weakFunc -- weak reference to the target function + + Class Attributes: + _allInstances -- class attribute pointing to all live + BoundMethodWeakref objects indexed by the class's + calculateKey(target) method applied to the target + objects. This weak value dictionary is used to + short-circuit creation so that multiple references + to the same (object, function) pair produce the + same BoundMethodWeakref instance. + + """ + + _allInstances = weakref.WeakValueDictionary() + + def __new__( cls, target, onDelete=None, *arguments,**named ): + """Create new instance or return current instance + + Basically this method of construction allows us to + short-circuit creation of references to already- + referenced instance methods. The key corresponding + to the target is calculated, and if there is already + an existing reference, that is returned, with its + deletionMethods attribute updated. Otherwise the + new instance is created and registered in the table + of already-referenced methods. + """ + key = cls.calculateKey(target) + current =cls._allInstances.get(key) + if current is not None: + current.deletionMethods.append( onDelete) + return current + else: + base = super( BoundMethodWeakref, cls).__new__( cls ) + cls._allInstances[key] = base + base.__init__( target, onDelete, *arguments,**named) + return base + + def __init__(self, target, onDelete=None): + """Return a weak-reference-like instance for a bound method + + target -- the instance-method target for the weak + reference, must have im_self and im_func attributes + and be reconstructable via: + target.im_func.__get__( target.im_self ) + which is true of built-in instance methods. + onDelete -- optional callback which will be called + when this weak reference ceases to be valid + (i.e. either the object or the function is garbage + collected). Should take a single argument, + which will be passed a pointer to this object. + """ + def remove(weak, self=self): + """Set self.isDead to true when method or instance is destroyed""" + methods = self.deletionMethods[:] + del self.deletionMethods[:] + try: + del self.__class__._allInstances[ self.key ] + except KeyError: + pass + for function in methods: + try: + if callable( function ): + function( self ) + except Exception, e: + try: + traceback.print_exc() + except AttributeError, err: + print '''Exception during saferef %s cleanup function %s: %s'''%( + self, function, e + ) + self.deletionMethods = [onDelete] + self.key = self.calculateKey( target ) + self.weakSelf = weakref.ref(target.im_self, remove) + self.weakFunc = weakref.ref(target.im_func, remove) + self.selfName = str(target.im_self) + self.funcName = str(target.im_func.__name__) + + def calculateKey( cls, target ): + """Calculate the reference key for this reference + + Currently this is a two-tuple of the id()'s of the + target object and the target function respectively. + """ + return (id(target.im_self),id(target.im_func)) + calculateKey = classmethod( calculateKey ) + + def __str__(self): + """Give a friendly representation of the object""" + return """%s( %s.%s )"""%( + self.__class__.__name__, + self.selfName, + self.funcName, + ) + + __repr__ = __str__ + + def __nonzero__( self ): + """Whether we are still a valid reference""" + return self() is not None + + def __cmp__( self, other ): + """Compare with another reference""" + if not isinstance (other,self.__class__): + return cmp( self.__class__, type(other) ) + return cmp( self.key, other.key) + + def __call__(self): + """Return a strong reference to the bound method + + If the target cannot be retrieved, then will + return None, otherwise returns a bound instance + method for our object and function. + + Note: + You may call this method any number of times, + as it does not invalidate the reference. + """ + target = self.weakSelf() + if target is not None: + function = self.weakFunc() + if function is not None: + return function.__get__(target) + return None + +class BoundNonDescriptorMethodWeakref(BoundMethodWeakref): + """A specialized BoundMethodWeakref, for platforms where instance methods + are not descriptors. + + It assumes that the function name and the target attribute name are the + same, instead of assuming that the function is a descriptor. This approach + is equally fast, but not 100% reliable because functions can be stored on an + attribute named differenty than the function's name such as in: + + class A: pass + def foo(self): return "foo" + A.bar = foo + + But this shouldn't be a common use case. So, on platforms where methods + aren't descriptors (such as Jython) this implementation has the advantage + of working in the most cases. + """ + def __init__(self, target, onDelete=None): + """Return a weak-reference-like instance for a bound method + + target -- the instance-method target for the weak + reference, must have im_self and im_func attributes + and be reconstructable via: + target.im_func.__get__( target.im_self ) + which is true of built-in instance methods. + onDelete -- optional callback which will be called + when this weak reference ceases to be valid + (i.e. either the object or the function is garbage + collected). Should take a single argument, + which will be passed a pointer to this object. + """ + assert getattr(target.im_self, target.__name__) == target, \ + ("method %s isn't available as the attribute %s of %s" % + (target, target.__name__, target.im_self)) + super(BoundNonDescriptorMethodWeakref, self).__init__(target, onDelete) + + def __call__(self): + """Return a strong reference to the bound method + + If the target cannot be retrieved, then will + return None, otherwise returns a bound instance + method for our object and function. + + Note: + You may call this method any number of times, + as it does not invalidate the reference. + """ + target = self.weakSelf() + if target is not None: + function = self.weakFunc() + if function is not None: + # Using curry() would be another option, but it erases the + # "signature" of the function. That is, after a function is + # curried, the inspect module can't be used to determine how + # many arguments the function expects, nor what keyword + # arguments it supports, and pydispatcher needs this + # information. + return getattr(target, function.__name__) + return None + +def get_bound_method_weakref(target, onDelete): + """Instantiates the appropiate BoundMethodWeakRef, depending on the details of + the underlying class method implementation""" + if hasattr(target, '__get__'): + # target method is a descriptor, so the default implementation works: + return BoundMethodWeakref(target=target, onDelete=onDelete) + else: + # no luck, use the alternative implementation: + return BoundNonDescriptorMethodWeakref(target=target, onDelete=onDelete) diff --git a/pilas/ejemplos/__init__.py b/pilas/ejemplos/__init__.py new file mode 100644 index 0000000..89f0399 --- /dev/null +++ b/pilas/ejemplos/__init__.py @@ -0,0 +1,7 @@ +from piezas import Piezas +from colisiones import Colisiones +from fisica import ColisionesFisicas + + +def ejecutar(): + print "asdasd" diff --git a/pilas/ejemplos/colisiones.py b/pilas/ejemplos/colisiones.py new file mode 100644 index 0000000..c8295ee --- /dev/null +++ b/pilas/ejemplos/colisiones.py @@ -0,0 +1,34 @@ +# -*- encoding: utf-8 -*- +# pilas engine - a video game framework. +# +# copyright 2010 - hugo ruscitti +# license: lgplv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# website - http://www.pilas-engine.com.ar + +import pilas + +def comer(mono, banana): + mono.sonreir() + banana.eliminar() + +class Colisiones(pilas.escenas.Normal): + """Es una escena que tiene un mono y algunas frutas para comer.""" + + + def __init__(self): + pilas.escenas.Normal.__init__(self, pilas.colores.grisoscuro) + self.crear_personajes() + + pilas.mundo.colisiones.agregar([self.mono], self.bananas, comer) + pilas.avisar("Utilice el mouse para mover al mono y comer") + + def crear_personajes(self): + self.mono = pilas.actores.Mono() + self.mono.aprender(pilas.habilidades.Arrastrable) + + self.bananas = pilas.atajos.fabricar(pilas.actores.Banana, 20) + + + + diff --git a/pilas/ejemplos/data/fondo_piezas.png b/pilas/ejemplos/data/fondo_piezas.png new file mode 100644 index 0000000..bec6841 --- /dev/null +++ b/pilas/ejemplos/data/fondo_piezas.png Binary files differ diff --git a/pilas/ejemplos/data/piezas.png b/pilas/ejemplos/data/piezas.png new file mode 100644 index 0000000..5c4ff8e --- /dev/null +++ b/pilas/ejemplos/data/piezas.png Binary files differ diff --git a/pilas/ejemplos/fisica.py b/pilas/ejemplos/fisica.py new file mode 100644 index 0000000..20b9d35 --- /dev/null +++ b/pilas/ejemplos/fisica.py @@ -0,0 +1,26 @@ +# -*- encoding: utf-8 -*- +# pilas engine - a video game framework. +# +# copyright 2010 - hugo ruscitti +# license: lgplv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# website - http://www.pilas-engine.com.ar + +import pilas + +class ColisionesFisicas(pilas.escenas.Normal): + + def __init__(self): + pilas.escenas.Normal.__init__(self, pilas.colores.grisoscuro) + pilas.avisar("Un ejemplo de colisiones") + + pilas.fondos.Pasto() + + m = pilas.actores.Mono() + m.aprender(pilas.habilidades.Arrastrable) + m.aprender(pilas.habilidades.ColisionableComoPelota) + + b = pilas.actores.Bomba() + b.aprender(pilas.habilidades.RebotarComoPelota) + + pilas.atajos.fabricar(pilas.actores.Pelota, 20) diff --git a/pilas/ejemplos/listaseleccion.py b/pilas/ejemplos/listaseleccion.py new file mode 100644 index 0000000..1ac1ac0 --- /dev/null +++ b/pilas/ejemplos/listaseleccion.py @@ -0,0 +1,12 @@ +import pilas +pilas.iniciar() + + +def cuando_selecciona(opcion): + print "Ha seleccionado la opcion:", opcion + + +consulta = pilas.interfaz.ListaSeleccion(['Uno', 'Dos', 'Tres'], cuando_selecciona) + +pilas.ejecutar() + diff --git a/pilas/ejemplos/piezas.py b/pilas/ejemplos/piezas.py new file mode 100644 index 0000000..e7cae75 --- /dev/null +++ b/pilas/ejemplos/piezas.py @@ -0,0 +1,247 @@ +# -*- encoding: utf-8 -*- +# pilas engine - a video game framework. +# +# copyright 2010 - hugo ruscitti +# license: lgplv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# website - http://www.pilas-engine.com.ar + +import pilas +import random + + +class Piezas(pilas.escenas.Normal): + """Representa la escena de rompecabezas. + + La escena comienza con una imagen que se descompone en muchos + actores Pieza. + """ + + def __init__(self, ruta_a_la_imagen="ejemplos/data/piezas.png", filas=4, columnas=4, al_terminar=None): + pilas.actores.utils.eliminar_a_todos() + pilas.escenas.Normal.__init__(self, pilas.colores.grisoscuro) + grilla = pilas.imagenes.cargar_grilla(ruta_a_la_imagen, columnas, filas) + self.crear_piezas(grilla, filas, columnas) + self.pieza_en_movimiento = None + + pilas.eventos.click_de_mouse.conectar(self.al_hacer_click) + pilas.eventos.termina_click.connect(self.al_soltar_el_click) + pilas.eventos.mueve_mouse.connect(self.al_mover_el_mouse) + + self.sonido_tick = pilas.sonidos.cargar("tick.wav") + self.al_terminar = al_terminar + self.piezas_desconectadas = filas * columnas -1 + + def crear_piezas(self, grilla, filas, columnas): + "Genera todas las piezas en base al tamaño del constructor." + self.piezas = [] + self.grupos = {} + + for x in range(filas * columnas): + self.grupos[x] = set([x]) + pieza = Pieza(self, grilla, x, filas, columnas) + self.piezas.append(pieza) + pieza.x = random.randint(-200, 200) + pieza.y = random.randint(-200, 200) + + + def al_hacer_click(self, evento): + "Atiente cualquier click que realice el usuario en la pantalla." + x, y = evento.x, evento.y + pieza_debajo_de_mouse = pilas.actores.utils.obtener_actor_en(x, y) + + if pieza_debajo_de_mouse and isinstance(pieza_debajo_de_mouse, Pieza): + self.pieza_en_movimiento = pieza_debajo_de_mouse + self.pieza_en_movimiento.mostrar_arriba_todas_las_piezas() + + def al_soltar_el_click(self, evento): + if self.pieza_en_movimiento: + self.pieza_en_movimiento.soltar_todas_las_piezas_del_grupo() + self.pieza_en_movimiento.mostrar_abajo_todas_las_piezas() + self.pieza_en_movimiento = None + + def al_mover_el_mouse(self, evento): + if self.pieza_en_movimiento: + self.pieza_en_movimiento.x += evento.dx + self.pieza_en_movimiento.y += evento.dy + + def conectar(self, pieza_a, pieza_b): + a = pieza_a.numero + b = pieza_b.numero + + + if a in self.grupos[b]: + #Evita contectar mas de una vez a dos piezas. + return + + """Inicialmente comienzo con:: + + + 0: [0, 1, 2] + 1: [0, 1, 2] + 2: [0, 1, 2] + 3: [3] + + ¿y si conecto la pieza 3 con la 2? + + - tendría que obtener todas las piezas que conoce 2. + + - iterar en ese grupo y decirle a cada pieza que sume a 3 en su grupo:: + + 0: [0, 1, 2, 3] + 1: [0, 1, 2, 3] + 2: [0, 1, 2, 3] + + - luego solo me falta tomar a uno de esos grupos actualizados + y decirle a 3 que ese será su grupo:: + + 3: [0, 1, 2, 3] + """ + + grupo_nuevo = set(self.grupos[a]).union(self.grupos[b]) + + for pieza in grupo_nuevo: + self.grupos[pieza] = grupo_nuevo + + self.piezas_desconectadas -= 1 + + if self.piezas_desconectadas < 1: + if self.al_terminar: + self.al_terminar() + + self.sonido_tick.reproducir() + +class Pieza(pilas.actores.Animado): + """Representa una pieza del rompecabezas. + + Esta pieza se puede arrastrar con el mouse y cuando se suelta + intentará conectarse con las demás.""" + + def __init__(self, escena_padre, grilla, cuadro, filas, columnas): + "Genera la pieza que representa una parte de la imagen completa." + self.escena_padre = escena_padre + self.numero = cuadro + pilas.actores.Animado.__init__(self, grilla) + + self.z_de_la_pieza_mas_alta = 0 + self.asignar_numero_de_piezas_laterales(cuadro, columnas) + + self.definir_cuadro(cuadro) + + self.radio_de_colision = self.obtener_ancho() / 2 + 12 + self.piezas_conectadas = [] + + def asignar_numero_de_piezas_laterales(self, cuadro, columnas): + "Guarda el numero de las piezas que se pueden conectar en sus bordes." + self.numero_arriba = cuadro - columnas + self.numero_abajo = cuadro + columnas + + if cuadro % columnas == 0: + self.numero_izquierda = -1 + else: + self.numero_izquierda = cuadro - 1 + + if cuadro % columnas == columnas -1: + self.numero_derecha = -1 + else: + self.numero_derecha = cuadro + 1 + + def soltar_todas_las_piezas_del_grupo(self): + for numero in self.escena_padre.grupos[self.numero]: + pieza = self.escena_padre.piezas[numero] + pieza.soltar() + + def soltar(self): + # Busca todas las colisiones entre esta pieza + # que se suelta y todas las demás. + colisiones = pilas.mundo.colisiones.obtener_colisiones(self, self.escena_padre.piezas) + + for x in colisiones: + self.intentar_conectarse_a(x) + + def se_pueden_conectar_los_bordes(self, borde1, borde2): + distancia = pilas.utils.distancia(borde1, borde2) + return distancia < 12 + + def intentar_conectarse_a(self, otra): + "Intenta vincular dos piezas, siempre y cuando coincidan en sus bordes." + + # Intenta conectar los bordes laterales + if self.numero_derecha == otra.numero: + if self.se_pueden_conectar_los_bordes(self.derecha, otra.izquierda): + otra.izquierda = self.derecha + otra.arriba = self.arriba + self.conectar_con(otra) + + elif self.numero_izquierda == otra.numero: + if self.se_pueden_conectar_los_bordes(self.izquierda, otra.derecha): + otra.derecha = self.izquierda + otra.arriba = self.arriba + self.conectar_con(otra) + + # Intenta conectar los bordes superior e inferior + if self.numero_abajo == otra.numero: + if self.se_pueden_conectar_los_bordes(self.abajo, otra.arriba): + otra.arriba = self.abajo + otra.izquierda = self.izquierda + self.conectar_con(otra) + + elif self.numero_arriba == otra.numero: + if self.se_pueden_conectar_los_bordes(self.arriba, otra.abajo): + otra.abajo = self.arriba + otra.izquierda = self.izquierda + self.conectar_con(otra) + + + def conectar_con(self, otra_pieza): + self.escena_padre.conectar(self, otra_pieza) + + + def __repr__(self): + return "<>" %(self.animacion.obtener_cuadro()) + + + def set_x(self, x): + "A diferencia de los actores normales, las piezas tienen que mover a todo su grupo." + dx = x - self.x + + for numero in self.escena_padre.grupos[self.numero]: + try: + pieza = self.escena_padre.piezas[numero] + pieza.definir_posicion(pieza.x + dx, pieza.y) + except IndexError: + pass + + def set_y(self, y): + "A diferencia de los actores normales, las piezas tienen que mover a todo su grupo." + dy = y - self.y + + for numero in self.escena_padre.grupos[self.numero]: + try: + pieza = self.escena_padre.piezas[numero] + pieza.definir_posicion(pieza.x, pieza.y + dy) + except IndexError: + pass + + def get_x(self): + x, y = self.obtener_posicion() + return x + + def get_y(self): + x, y = self.obtener_posicion() + return y + + x = property(get_x, set_x, doc="Define la posición horizontal.") + y = property(get_y, set_y, doc="Define la posición vertical.") + + def mostrar_arriba_todas_las_piezas(self): + for numero in self.escena_padre.grupos[self.numero]: + pieza = self.escena_padre.piezas[numero] + pieza.z = -1 + + def mostrar_abajo_todas_las_piezas(self): + for numero in self.escena_padre.grupos[self.numero]: + pieza = self.escena_padre.piezas[numero] + pieza.z = 0 + + diff --git a/pilas/ejemplos/test.py b/pilas/ejemplos/test.py new file mode 100644 index 0000000..3e98456 --- /dev/null +++ b/pilas/ejemplos/test.py @@ -0,0 +1,40 @@ +# -*- encoding: utf-8 -*- +import random +import unittest +import pilas + +class MockEscena: + + def __init__(self): + self.grupos = {1: [], + 8: []} + +class TestPiezas(unittest.TestCase): + + def test_pieza_de_ejemplo(self): + + filas = 3 + columnas = 3 + + mock_escena = MockEscena() + + grilla = pilas.imagenes.carga_grilla("ejemplos/data/piezas.png", filas, columnas) + p = pilas.ejemplos.piezas.Pieza(mock_escena, grilla, 1, filas, columnas) + + self.assertEqual(p.numero, 1) + self.assertEqual(p.numero_derecha, 2) + self.assertEqual(p.numero_izquierda, 0) + self.assertTrue(p.numero_arriba < 0) + self.assertEqual(p.numero_abajo, 4) + + p = pilas.ejemplos.piezas.Pieza(mock_escena, grilla, 8, filas, columnas) + + self.assertEqual(p.numero, 8) + self.assertEqual(p.numero_derecha, -1) # la pieza 8 no tiene borde derecho. + self.assertEqual(p.numero_izquierda, 7) + self.assertEqual(p.numero_arriba, 5) + self.assertEqual(p.numero_abajo, 11) # la pieza 8 no tiene parte de abajo + +if __name__ == '__main__': + pilas.iniciar() + unittest.main() diff --git a/pilas/escenas.py b/pilas/escenas.py new file mode 100644 index 0000000..e7e081f --- /dev/null +++ b/pilas/escenas.py @@ -0,0 +1,30 @@ +# -*- encoding: utf-8 -*- +# pilas engine - a video game framework. +# +# copyright 2010 - hugo ruscitti +# license: lgplv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# website - http://www.pilas-engine.com.ar + +import pilas + +class Escena(object): + "Escena abstracta." + + def __init__(self): + pilas.actores.utils.destruir_a_todos() + pilas.mundo.definir_escena(self) + + def iniciar(self): + pass + + def terminar(self): + pass + + +class Normal(Escena): + "Representa la escena inicial mas simple." + + def __init__(self, color_de_fondo=None): + Escena.__init__(self) + self.fondo = pilas.fondos.Color(color_de_fondo) diff --git a/pilas/estudiante.py b/pilas/estudiante.py new file mode 100644 index 0000000..0322a8a --- /dev/null +++ b/pilas/estudiante.py @@ -0,0 +1,73 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +import pilas.utils + +class Estudiante: + """Representa la habilidad de poder realizar habiliadades y comportamientos.""" + + def __init__(self): + self.habilidades = [] + self.comportamiento_actual = None + self.comportamientos = [] + self.repetir_comportamientos_por_siempre = False + + def aprender(self, classname, *k, **w): + "Comienza a realizar una habilidad indicada por parametros." + objeto_habilidad = classname(self, *k, **w) + self.habilidades.append(objeto_habilidad) + + def hacer_luego(self, comportamiento, repetir_por_siempre=False): + """Define un nuevo comportamiento para realizar al final. + + Los actores pueden tener una cadena de comportamientos, este + metodo agrega el comportamiento al final de la cadena. + """ + + self.comportamientos.append(comportamiento) + self.repetir_comportamientos_por_siempre = repetir_por_siempre + + def hacer(self, comportamiento): + "Define el comportamiento para el actor de manera inmediata." + self.comportamientos.append(comportamiento) + self._adoptar_el_siguiente_comportamiento() + + def eliminar_habilidades(self): + "Elimina todas las habilidades asociadas al actor." + for h in self.habilidades: + h.eliminar() + + def eliminar_comportamientos(self): + "Elimina todos los comportamientos que tiene que hacer el actor." + for c in self.comportamientos: + c.eliminar() + + def actualizar_habilidades(self): + for h in self.habilidades: + h.actualizar() + + def actualizar_comportamientos(self): + termina = None + + if self.comportamiento_actual: + termina = self.comportamiento_actual.actualizar() + + if termina: + if self.repetir_comportamientos_por_siempre: + self.comportamientos.append(self.comportamiento_actual) + self._adoptar_el_siguiente_comportamiento() + else: + self._adoptar_el_siguiente_comportamiento() + + def _adoptar_el_siguiente_comportamiento(self): + if self.comportamientos: + self.comportamiento_actual = self.comportamientos.pop(0) + self.comportamiento_actual.iniciar(self) + else: + self.comportamiento_actual = None + diff --git a/pilas/eventos.py b/pilas/eventos.py new file mode 100644 index 0000000..d7a78bd --- /dev/null +++ b/pilas/eventos.py @@ -0,0 +1,42 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +import dispatch + +mueve_mouse = dispatch.Signal(providing_args=['x', 'y', 'dx', 'dy']) +click_de_mouse = dispatch.Signal(providing_args=['button', 'x', 'y']) +termina_click = dispatch.Signal(providing_args=['button', 'x', 'y']) +mueve_rueda = dispatch.Signal(providing_args=['delta']) +pulsa_tecla = dispatch.Signal(providing_args=['codigo', 'texto']) +suelta_tecla = dispatch.Signal(providing_args=['codigo', 'texto']) +pulsa_tecla_escape = dispatch.Signal(providing_args=[]) +actualizar = dispatch.Signal(providing_args=[]) +post_dibujar = dispatch.Signal(providing_args=[]) + +# Se emite cuando el mundo ingresa o sale del modo depuracion (pulsando F12) +inicia_modo_depuracion = dispatch.Signal(providing_args=[]) +sale_modo_depuracion = dispatch.Signal(providing_args=[]) +actualiza_modo_depuracion = dispatch.Signal(providing_args=[]) + + +def imprimir_todos(): + "Muestra en consola los eventos activos y a quienes invocan" + imprime_alguno = False + + for x in globals().items(): + nombre = x[0] + evento = x[1] + + if isinstance(evento, dispatch.Signal): + if evento.esta_conectado(): + imprime_alguno = True + print "%s:" %(nombre) + evento.imprimir_funciones_conectadas() + + if not imprime_alguno: + print "Ningun evento esta conectado." diff --git a/pilas/fisica.py b/pilas/fisica.py new file mode 100644 index 0000000..b13ad6e --- /dev/null +++ b/pilas/fisica.py @@ -0,0 +1,452 @@ +# -*- encoding: utf-8 -*- +# pilas engine - a video game framework. +# +# copyright 2010 - hugo ruscitti +# license: lgplv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# website - http://www.pilas-engine.com.ar + +import pilas +from pilas import colores + +try: + import Box2D as box2d +except ImportError: + print "No esta disponible box2d, se deshabilitara la fisica." + +import math + +class Fisica(object): + """Representa un simulador de mundo fisico, usando la biblioteca box2d.""" + + def __init__(self, area, gravedad=(0, -90)): + self.area = area + try: + self.escenario = box2d.b2AABB() + self.escenario.lowerBound = (-1000.0, -1000.0) + self.escenario.upperBound = (1000.0, 1000.0) + self.gravedad = box2d.b2Vec2(gravedad[0], gravedad[1]) + try: + self.mundo = box2d.b2World(self.escenario, self.gravedad, True) + except ValueError: + print "Solo esta disponible el motor de fisica para box2d 2.0.2b1" + raise AttributeError("...") + except AttributeError: + print "Deshabilitando modulo de fisica (no se encuentra instalado pybox2d en este equipo)" + self.mundo = None + return + + self.constante_mouse = None + self.i = 0 + self.crear_bordes_del_escenario() + self.figuras_a_eliminar = [] + + def crear_bordes_del_escenario(self): + self.crear_techo(self.area) + self.crear_suelo(self.area) + self.crear_paredes(self.area) + + def reiniciar(self): + for x in self.mundo.bodyList: + self.mundo.DestroyBody(x) + + self.crear_bordes_del_escenario() + + def capturar_figura_con_el_mouse(self, figura): + if self.constante_mouse: + self.cuando_suelta_el_mouse() + + self.constante_mouse = ConstanteDeMovimiento(figura) + + def cuando_mueve_el_mouse(self, x, y): + if self.constante_mouse: + self.constante_mouse.mover(x, y) + + def cuando_suelta_el_mouse(self): + if self.constante_mouse: + self.constante_mouse.eliminar() + self.constante_mouse = None + + def actualizar(self): + if self.mundo: + self.mundo.Step(1.0 / 20.0, 10, 8) + self.i += 1 + self._procesar_figuras_a_eliminar() + + def _procesar_figuras_a_eliminar(self): + "Elimina las figuras que han sido marcadas para quitar." + if self.figuras_a_eliminar: + for x in self.figuras_a_eliminar: + # Solo elimina las figuras que actualmente existen. + if x in self.mundo.bodyList: + self.mundo.DestroyBody(x) + self.figuras_a_eliminar = [] + + def dibujar_figuras_sobre_lienzo(self, motor, lienzo, grosor=1): + "Dibuja todas las figuras en una pizarra. Indicado para depuracion." + cuerpos = self.mundo.bodyList + cantidad_de_figuras = 0 + + for cuerpo in cuerpos: + xform = cuerpo.GetXForm() + + for figura in cuerpo.shapeList: + cantidad_de_figuras += 1 + tipo_de_figura = figura.GetType() + + if tipo_de_figura == box2d.e_polygonShape: + vertices = [] + + for v in figura.vertices: + pt = box2d.b2Mul(xform, v) + vertices.append((pt.x, pt.y)) + + lienzo.poligono(motor, vertices, color=colores.rojo, grosor=grosor, cerrado=True) + + elif tipo_de_figura == box2d.e_circleShape: + lienzo.circulo(motor, cuerpo.position.x, cuerpo.position.y, figura.radius, colores.rojo, grosor=grosor) + else: + print "no puedo identificar el tipo de figura." + + def crear_cuerpo(self, definicion_de_cuerpo): + return self.mundo.CreateBody(definicion_de_cuerpo) + + def crear_suelo(self, (ancho, alto), restitucion=0): + self.suelo = Rectangulo(0, -alto/2, ancho, 2, dinamica=False, fisica=self, restitucion=restitucion) + + def crear_techo(self, (ancho, alto), restitucion=0): + self.techo = Rectangulo(0, alto/2, ancho, 2, dinamica=False, fisica=self, restitucion=restitucion) + + def crear_paredes(self, (ancho, alto), restitucion=0): + self.pared_izquierda = Rectangulo(-ancho/2, 0, 2, alto, dinamica=False, fisica=self, restitucion=restitucion) + self.pared_derecha = Rectangulo(ancho/2, 0, 2, alto, dinamica=False, fisica=self, restitucion=restitucion) + + def eliminar_suelo(self): + if self.suelo: + self.suelo.eliminar() + self.suelo = None + + def eliminar_techo(self): + if self.techo: + self.techo.eliminar() + self.techo = None + + def eliminar_paredes(self): + if self.pared_izquierda: + self.pared_derecha.eliminar() + self.pared_izquierda.eliminar() + self.pared_derecha = None + self.pared_izquierda = None + + def eliminar_figura(self, figura): + self.figuras_a_eliminar.append(figura) + + def obtener_distancia_al_suelo(self, x, y, dy): + """Obtiene la distancia hacia abajo desde el punto (x,y). + + El valor de 'dy' tiene que ser positivo. + + Si la funcion no encuentra obstaculos retornara + dy, pero en paso contrario retornara un valor menor + a dy. + """ + + if dy < 0: + raise Exception("El valor de 'dy' debe ser positivo, ahora vale '%f'." %(dy)) + + delta = 0 + + while delta < dy: + + if self.obtener_cuerpos_en(x, y-delta): + return delta + + delta += 1 + + return delta + + def obtener_cuerpos_en(self, x, y): + "Retorna una lista de cuerpos que se encuentran en la posicion (x, y) o retorna una lista vacia []." + + AABB = box2d.b2AABB() + f = 1 + AABB.lowerBound = (x-f, y-f) + AABB.upperBound = (x+f, y+f) + + cuantos, cuerpos = self.mundo.Query(AABB, 2) + + if cuantos == 0: + return [] + + lista_de_cuerpos = [] + + for s in cuerpos: + cuerpo = s.GetBody() + + if s.TestPoint(cuerpo.GetXForm(), (x, y)): + lista_de_cuerpos.append(cuerpo) + + return lista_de_cuerpos + + def definir_gravedad(self, x, y): + pilas.fisica.definir_gravedad(x, y) + +class Figura(object): + """Representa un figura que simula un cuerpo fisico. + + Esta figura es abstracta, no está pensada para crear + objetos a partir de ella. Se usa como base para el resto + de las figuras cómo el Circulo o el Rectangulo simplemente.""" + + def obtener_x(self): + return self._cuerpo.position.x + + def definir_x(self, x): + self._cuerpo.SetXForm((x, self.y), self._cuerpo.GetAngle()) + + def obtener_y(self): + return self._cuerpo.position.y + + def definir_y(self, y): + self._cuerpo.SetXForm((self.x, y), self._cuerpo.GetAngle()) + + def obtener_rotacion(self): + return - math.degrees(self._cuerpo.GetAngle()) + + def definir_rotacion(self, angulo): + self._cuerpo.SetXForm((self.x, self.y), math.radians(-angulo)) + + def impulsar(self, dx, dy): + self._cuerpo.ApplyImpulse((dx, dy), self._cuerpo.GetWorldCenter()) + + def obtener_velocidad_lineal(self): + velocidad = self._cuerpo.GetLinearVelocity() + return (velocidad.x, velocidad.y) + + def detener(self): + """Hace que la figura regrese al reposo.""" + self.definir_velocidad_lineal(0, 0) + + def definir_velocidad_lineal(self, dx=None, dy=None): + anterior_dx, anterior_dy = self.obtener_velocidad_lineal() + + if dx is None: + dx = anterior_dx + if dy is None: + dy = anterior_dy + + self._cuerpo.SetLinearVelocity((dx, dy)) + + def empujar(self, dx=None, dy=None): + self.definir_velocidad_lineal(dx, dy) + + def eliminar(self): + """Quita una figura de la simulación.""" + pilas.mundo.fisica.eliminar_figura(self._cuerpo) + + x = property(obtener_x, definir_x, doc="define la posición horizontal.") + y = property(obtener_y, definir_y, doc="define la posición vertical.") + rotacion = property(obtener_rotacion, definir_rotacion, doc="define la rotacion.") + +class Circulo(Figura): + """Representa un cuerpo de circulo. + + Generalmente estas figuras se pueden construir independientes de un + actor, y luego asociar. + + Por ejemplo, podríamos crear un círculo: + + >>> circulo_dinamico = pilas.fisica.Circulo(10, 200, 50) + + y luego tomar un actor cualquiera, y decirle que se comporte + cómo el circulo: + + >>> mono = pilas.actores.Mono() + >>> mono.imitar(circulo_dinamico) + """ + + def __init__(self, x, y, radio, dinamica=True, densidad=1.0, + restitucion=0.56, friccion=10.5, amortiguacion=0.1, + fisica=None): + + if not fisica: + fisica = pilas.mundo.fisica + + bodyDef = box2d.b2BodyDef() + bodyDef.position=(x, y) + bodyDef.linearDamping = amortiguacion + #userData = { 'color' : self.parent.get_color() } + #bodyDef.userData = userData + #self.parent.element_count += 1 + + body = fisica.crear_cuerpo(bodyDef) + + # Create the Body + if not dinamica: + densidad = 0 + + # Add a shape to the Body + circleDef = box2d.b2CircleDef() + circleDef.density = densidad + circleDef.radius = radio + circleDef.restitution = restitucion + circleDef.friction = friccion + + body.CreateShape(circleDef) + body.SetMassFromShapes() + + self._cuerpo = body + +class Rectangulo(Figura): + """Representa un rectángulo que puede colisionar con otras figuras. + + Se puede crear un rectángulo independiente y luego asociarlo + a un actor de la siguiente forma: + + >>> rect = pilas.fisica.Rectangulo(50, 90, True) + >>> actor = pilas.actores.Pingu() + >>> actor.imitar(rect) + """ + + def __init__(self, x, y, ancho, alto, dinamica=True, densidad=1.0, + restitucion=0.56, friccion=10.5, amortiguacion=0.1, + fisica=None): + + if not fisica: + fisica = pilas.mundo.fisica + + bodyDef = box2d.b2BodyDef() + bodyDef.position=(x, y) + bodyDef.linearDamping = amortiguacion + + #userData = { 'color' : self.parent.get_color() } + #bodyDef.userData = userData + #self.parent.element_count += 1 + + body = fisica.crear_cuerpo(bodyDef) + + # Create the Body + if not dinamica: + densidad = 0 + + # Add a shape to the Body + boxDef = box2d.b2PolygonDef() + + boxDef.SetAsBox(ancho/2, alto/2, (0,0), 0) + boxDef.density = densidad + boxDef.restitution = restitucion + boxDef.friction = friccion + body.CreateShape(boxDef) + + body.SetMassFromShapes() + + self._cuerpo = body + + +class Poligono(Figura): + """Representa un cuerpo poligonal. + + El poligono necesita al menos tres puntos para dibujarse, y cada + uno de los puntos se tienen que ir dando en orden de las agujas + del relog. + + Por ejemplo: + + >>> pilas.fisica.Poligono([(100, 2), (-50, 0), (-100, 100.0)]) + + """ + + def __init__(self, puntos, dinamica=True, densidad=1.0, + restitucion=0.56, friccion=10.5, amortiguacion=0.1, + fisica=None): + + if not fisica: + fisica = pilas.mundo.fisica + + bodyDef = box2d.b2BodyDef() + bodyDef.position=puntos[0] + bodyDef.linearDamping = amortiguacion + body = fisica.crear_cuerpo(bodyDef) + + # Create the Body + if not dinamica: + densidad = 0 + + if len(puntos) < 3: + raise Exception("Tienes que definir al menos 3 puntos para tener un poligono") + + # Add a shape to the Body + poligono_def = box2d.b2PolygonDef() + puntos.reverse() + poligono_def.setVertices(puntos) + + poligono_def.density = densidad + poligono_def.restitution = restitucion + poligono_def.friction = friccion + #poligono_def.setVertices(puntos) + #poligono_def.vertexCount = len(puntos) + + #for indice, punto in enumerate(puntos): + # poligono_def.setVertex(indice, punto[0], punto[1]) + # #poligono_def.vertices[indice] = punto + + body.CreateShape(poligono_def) + body.SetMassFromShapes() + self._cuerpo = body + +class ConstanteDeMovimiento(): + + def __init__(self, figura): + md = box2d.b2MouseJointDef() + mundo = pilas.mundo.fisica.mundo + md.body1 = mundo.GetGroundBody() + md.body2 = figura._cuerpo + md.target = (figura.x, figura.y) + md.maxForce = 5000.0 * figura._cuerpo.GetMass() + + try: + self.constante = mundo.CreateJoint(md).getAsType() + except: + self.constante = mundo.CreateJoint(md) + + figura._cuerpo.WakeUp() + + def mover(self, x, y): + self.constante.SetTarget((x, y)) + + def eliminar(self): + pilas.mundo.fisica.mundo.DestroyJoint(self.constante) + +class ConstanteDeDistancia(): + """Representa una distancia fija entre dos figuras. + + Esta constante es útil para representar ejes o barras + que sostienen dos cuerpos. Por ejemplo, un eje entre dos + ruedas en un automóvil: + + >>> circulo_1 = pilas.fisica.Circulo(-100, 0, 50) + >>> circulo_2 = pilas.fisica.Circulo(100, 50, 50) + >>> barra = pilas.fisica.ConstanteDeDistancia(circulo_1, circulo_2) + + La distancia que tiene que respetarse en la misma que tienen + las figuras en el momento en que se establece la constante. + """ + + def __init__(self, figura_1, figura_2, fisica=None): + if not fisica: + fisica = pilas.mundo.fisica + + if not isinstance(figura_1, Figura) or not isinstance(figura_2, Figura): + raise Exception("Las dos figuras tienen que ser objetos de la clase Figura.") + + constante = box2d.b2DistanceJointDef() + constante.Initialize(figura_1._cuerpo, figura_2._cuerpo, (0,0), (0,0)) + constante.collideConnected = True + self.constante = fisica.mundo.CreateJoint(constante) + + + def eliminar(self): + pilas.mundo.fisica.mundo.DestroyJoint(self.constante_mouse) + +def definir_gravedad(x=0, y=-90): + pilas.mundo.fisica.mundo.gravity = (x, y) diff --git a/pilas/fondos.py b/pilas/fondos.py new file mode 100644 index 0000000..4380ceb --- /dev/null +++ b/pilas/fondos.py @@ -0,0 +1,64 @@ +# -*- encoding: utf-8 -*- +# pilas engine - a video game framework. +# +# copyright 2010 - hugo ruscitti +# license: lgplv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# website - http://www.pilas-engine.com.ar + +import pilas + +class Fondo(pilas.actores.Actor): + + def __init__(self, imagen): + pilas.actores.Actor.__init__(self, imagen) + self.z = 1000 + +class Volley(Fondo): + "Muestra una escena que tiene un fondo de pantalla de paisaje." + + def __init__(self): + Fondo.__init__(self, "fondos/volley.jpg") + +class Pasto(Fondo): + "Muestra una escena que tiene un fondo de pantalla de paisaje." + + def __init__(self): + Fondo.__init__(self, "fondos/pasto.png") + +class Selva(Fondo): + "Muestra una escena que tiene un fondo de pantalla de paisaje." + + def __init__(self): + Fondo.__init__(self, "fondos/selva.jpg") + + +class Tarde(Fondo): + "Representa una escena de fondo casi naranja." + + def __init__(self): + Fondo.__init__(self, "fondos/tarde.jpg") + + +class Espacio(Fondo): + "Es un espacio con estrellas." + + def __init__(self): + Fondo.__init__(self, "fondos/espacio.jpg") + +class Noche(Fondo): + "Muestra una escena que tiene un fondo de pantalla de paisaje." + + def __init__(self): + Fondo.__init__(self, "fondos/noche.jpg") + +class Color(Fondo): + + def __init__(self, color): + Fondo.__init__(self, "invisible.png") + self.color = color + self.lienzo = pilas.imagenes.cargar_lienzo() + + def dibujar(self, motor): + if self.color: + self.lienzo.pintar(motor, self.color) diff --git a/pilas/fps.py b/pilas/fps.py new file mode 100644 index 0000000..943ae6a --- /dev/null +++ b/pilas/fps.py @@ -0,0 +1,57 @@ +#import pygame +import time +import pygame + +class ___FPS: + + def __init__(self, fps, usar_modo_economico): + pass + + def actualizar(self): + time.sleep(1 / 40.0) + + def obtener_cuadros_por_segundo(self): + return 0 + +class FPS(object): + #print "Usando pygame en el modulo fps" + + def __init__(self, fps, usar_modo_economico): + self.antes = self.ahora = pygame.time.get_ticks() + self.frecuencia = 1000.0 / fps + self.t_fps = self.ahora + self.rendimiento = 0 + self.cuadros_por_segundo = "??" + self.usar_modo_economico = usar_modo_economico + + def actualizar(self): + retorno = 0 + self.ahora = pygame.time.get_ticks() + + dt = self.ahora - self.antes + + while dt >= self.frecuencia: + self.antes += self.frecuencia + dt = self.ahora - self.antes + retorno += 1 + + if self.ahora - self.t_fps > 1000.0: + #print self.cuadros_por_segundo + self.cuadros_por_segundo = str(self.rendimiento) + self.t_fps += 1000.0 + self.rendimiento = 0 + else: + if self.usar_modo_economico: + pygame.time.wait(int(self.frecuencia - dt)) + else: + pygame.time.delay(int(self.frecuencia - dt)) + + returno = 1 + + + self.rendimiento += 1 + return retorno + + + def obtener_cuadros_por_segundo(self): + return self.cuadros_por_segundo diff --git a/pilas/grupo.py b/pilas/grupo.py new file mode 100644 index 0000000..466ad46 --- /dev/null +++ b/pilas/grupo.py @@ -0,0 +1,48 @@ +# -*- encoding: utf-8 -*- +# pilas engine - a video game framework. +# +# copyright 2010 - hugo ruscitti +# license: lgplv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# website - http://www.pilas-engine.com.ar +import random + +class Grupo(list): + """Un grupo es un contenedor que funciona como una lista normal, pero mejorada. + + Los grupos pueden contener actores, y permite que a todos los actores + se los pueda tratar como uno. + + Por ejemplo si tienes un contenedor con 20 actores, podrías ampliar + el tamaño de todos ellos juntos usando la sentencia:: + + grupo = pilas.atajos.fabricar(pilas.actores.Mono, 20) + grupo.escala = 2 + + """ + + def __getattr__(self, attr): + """Esta funcion se asegura de que cada vez que se invoque a un metodo + del grupo, en realidad, el grupo va a invocar a ese metodo pero + en todos sus elementos. Algo asi como un map.""" + + def map_a_todos(*k, **kw): + for a in self: + funcion = getattr(a, attr) + funcion(*k, **kw) + + return map_a_todos + + def __setattr__(self, atributo, valor): + for a in self: + setattr(a, atributo, valor) + + def desordenar(self): + for a in self: + a.x = random.randint(-300, 300) + a.y = random.randint(-200, 200) + + def limpiar(self): + eliminar = list(self) + for e in eliminar: + e.eliminar() diff --git a/pilas/habilidades.py b/pilas/habilidades.py new file mode 100644 index 0000000..74d3ba9 --- /dev/null +++ b/pilas/habilidades.py @@ -0,0 +1,251 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +import random +import pilas + + +class Habilidad(object): + + def __init__(self, receptor): + self.receptor = receptor + + def actualizar(self): + pass + + def eliminar(self): + pass + +class RebotarComoPelota(Habilidad): + + def __init__(self, receptor): + Habilidad.__init__(self, receptor) + error = random.randint(-10, 10) / 10.0 + + circulo = pilas.fisica.Circulo(receptor.x + error, + receptor.y + error, + receptor.radio_de_colision) + receptor.aprender(pilas.habilidades.Imitar, circulo) + self.circulo = circulo + receptor.impulsar = self.impulsar + receptor.empujar = self.empujar + + def eliminar(self): + self.circulo.eliminar() + + def impulsar(self, dx, dy): + self.circulo.impulsar(dx, dy) + + def empujar(self, dx, dy): + self.circulo.empujar(dx, dy) + +class RebotarComoCaja(Habilidad): + + def __init__(self, receptor): + Habilidad.__init__(self, receptor) + error = random.randint(-10, 10) / 10.0 + rectangulo = pilas.fisica.Rectangulo(receptor.x + error, + receptor.y + error, + receptor.radio_de_colision*2 - 4, + receptor.radio_de_colision*2 - 4, + ) + receptor.aprender(pilas.habilidades.Imitar, rectangulo) + self.rectangulo = rectangulo + + def eliminar(self): + self.rectangulo.eliminar() + + +class ColisionableComoPelota(RebotarComoPelota): + + def __init__(self, receptor): + RebotarComoPelota.__init__(self, receptor) + + def actualizar(self): + self.figura.body.position.x = self.receptor.x + self.figura.body.position.y = self.receptor.y + + def eliminar(self): + pilas.fisica.fisica.eliminar(self.figura) + +class SeguirAlMouse(Habilidad): + "Hace que un actor siga la posición del mouse en todo momento." + + def __init__(self, receptor): + Habilidad.__init__(self, receptor) + pilas.eventos.mueve_mouse.connect(self.mover) + + def mover(self, evento): + self.receptor.x = evento.x + self.receptor.y = evento.y + +class AumentarConRueda(Habilidad): + "Permite cambiar el tamaño de un actor usando la ruedita scroll del mouse." + + def __init__(self, receptor): + Habilidad.__init__(self, receptor) + pilas.eventos.mueve_rueda.connect(self.cambiar_de_escala) + + def cambiar_de_escala(self, evento): + self.receptor.escala += (evento.delta / 4.0) + + +class SeguirClicks(Habilidad): + "Hace que el actor se coloque la posición del cursor cuando se hace click." + + def __init__(self, receptor): + Habilidad.__init__(self, receptor) + pilas.eventos.click_de_mouse.connect(self.moverse_a_este_punto) + + def moverse_a_este_punto(self, evento): + self.receptor.x = [evento.x], 0.5 + self.receptor.y = [evento.y], 0.5 + + +class Arrastrable(Habilidad): + """Hace que un objeto se pueda arrastrar con el puntero del mouse. + + Cuando comienza a mover al actor se llama al metodo ''comienza_a_arrastrar'' + y cuando termina llama a ''termina_de_arrastrar''. Estos nombres + de metodos se llaman para que puedas personalizar estos eventos, dado + que puedes usar polimorfismo para redefinir el comportamiento + de estos dos metodos. Observa un ejemplo de esto en + el ejemplo ``pilas.ejemplos.Piezas``. + """ + + def __init__(self, receptor): + Habilidad.__init__(self, receptor) + pilas.eventos.click_de_mouse.connect(self.cuando_intenta_arrastrar) + + def cuando_intenta_arrastrar(self, evento): + "Intenta mover el objeto con el mouse cuando se pulsa sobre el." + if self.receptor.colisiona_con_un_punto(evento.x, evento.y): + pilas.eventos.termina_click.connect(self.cuando_termina_de_arrastrar) + pilas.eventos.mueve_mouse.connect(self.cuando_arrastra, uid='cuando_arrastra') + self.comienza_a_arrastrar() + + def cuando_arrastra(self, evento): + "Arrastra el actor a la posicion indicada por el puntero del mouse." + if self._el_receptor_tiene_fisica(): + pilas.mundo.fisica.cuando_mueve_el_mouse(evento.x, evento.y) + else: + self.receptor.x += evento.dx + self.receptor.y += evento.dy + + def cuando_termina_de_arrastrar(self, evento): + "Suelta al actor porque se ha soltado el botón del mouse." + pilas.eventos.mueve_mouse.disconnect(uid='cuando_arrastra') + self.termina_de_arrastrar() + + def comienza_a_arrastrar(self): + if self._el_receptor_tiene_fisica(): + pilas.mundo.fisica.capturar_figura_con_el_mouse(self.receptor.figura) + + def termina_de_arrastrar(self): + if self._el_receptor_tiene_fisica(): + pilas.mundo.fisica.cuando_suelta_el_mouse() + + def _el_receptor_tiene_fisica(self): + return hasattr(self.receptor, 'figura') + + +class MoverseConElTeclado(Habilidad): + "Hace que un actor cambie de posición con pulsar el teclado." + + def __init__(self, receptor): + Habilidad.__init__(self, receptor) + pilas.eventos.actualizar.connect(self.on_key_press) + + def on_key_press(self, evento): + velocidad = 5 + c = pilas.mundo.control + + if c.izquierda: + self.receptor.x -= velocidad + elif c.derecha: + self.receptor.x += velocidad + + if c.arriba: + self.receptor.y += velocidad + elif c.abajo: + self.receptor.y -= velocidad + +class PuedeExplotar(Habilidad): + "Hace que un actor se pueda hacer explotar invocando al metodo eliminar." + + def __init__(self, receptor): + Habilidad.__init__(self, receptor) + receptor.eliminar = self.eliminar_y_explotar + + def eliminar_y_explotar(self): + explosion = pilas.actores.Explosion() + explosion.x = self.receptor.x + explosion.y = self.receptor.y + explosion.escala = self.receptor.escala * 2 + pilas.actores.Actor.eliminar(self.receptor) + + +class SeMantieneEnPantalla(Habilidad): + """Se asegura de que el actor regrese a la pantalla si sale. + + Si el actor sale por la derecha de la pantalla, entonces regresa + por la izquiera. Si sale por arriba regresa por abajo y asi...""" + + def actualizar(self): + # Se asegura de regresar por izquierda y derecha. + if self.receptor.derecha < -320: + self.receptor.izquierda = 320 + elif self.receptor.izquierda > 320: + self.receptor.derecha = -320 + + # Se asegura de regresar por arriba y abajo. + if self.receptor.abajo > 240: + self.receptor.arriba = -240 + elif self.receptor.arriba < -240: + self.receptor.abajo = 240 + + +class PisaPlataformas(Habilidad): + + def __init__(self, receptor): + Habilidad.__init__(self, receptor) + error = random.randint(-10, 10) / 10.0 + self.figura = pilas.fisica.fisica.crear_figura_cuadrado(receptor.x + error, + receptor.y + error, + receptor.radio_de_colision, + masa=10, + elasticidad=0, + friccion=0) + self.ultimo_x = receptor.x + self.ultimo_y = receptor.y + + def actualizar(self): + # Mueve el objeto siempre y cuando no parezca que algo + # no fisico (es decir de pymunk) lo ha afectado. + self.receptor.x = self.figura.body.position.x + self.receptor.y = self.figura.body.position.y + + def eliminar(self): + pilas.fisica.fisica.eliminar(self.figura) + +class Imitar(Habilidad): + + def __init__(self, receptor, objeto_a_imitar): + Habilidad.__init__(self, receptor) + self.objeto_a_imitar = objeto_a_imitar + receptor.figura = objeto_a_imitar + + def actualizar(self): + self.receptor.x = self.objeto_a_imitar.x + self.receptor.y = self.objeto_a_imitar.y + self.receptor.rotacion = self.objeto_a_imitar.rotacion + + def eliminar(self): + if isinstance(self.objeto_a_imitar, pilas.fisica.Figura): + self.objeto_a_imitar.eliminar() + diff --git a/pilas/imagenes.py b/pilas/imagenes.py new file mode 100644 index 0000000..163445f --- /dev/null +++ b/pilas/imagenes.py @@ -0,0 +1,77 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +import pilas +import os + +def cargar(ruta): + """Intenta cargar la imagen indicada por el argumento ``ruta``. + + Por ejemplo:: + + import pilas + + imagen = pilas.imagenes.cargar("mi_archivo.png") + + En caso de éxito retorna el objeto Image, que se puede asignar + a un Actor. + + El directorio de búsqueda de la imagen sigue el siguiente orden: + + * primero busca en el directorio actual. + * luego en 'data'. + * por último en el directorio estándar de la biblioteca. + + En caso de error genera una excepción de tipo IOError. + """ + + if not pilas.mundo: + mensaje = "Tiene que invocar a la funcion ``pilas.iniciar()`` para comenzar." + print mensaje + raise Exception(mensaje) + + ruta = pilas.utils.obtener_ruta_al_recurso(ruta) + return pilas.mundo.motor.cargar_imagen(ruta) + +def cargar_grilla(ruta, columnas=1, filas=1): + """Representa una grilla de imagenes con varios cuadros de animación. + + Una grilla es un objeto que se tiene que inicializar con la ruta + a una imagen, la cantidad de columnas y filas. + + Por ejemplo, si tenemos una grilla con 2 columnas y 3 filas + podemos asociarla a un actor de la siguiente manera:: + + grilla = pilas.imagenes.cargar_grilla("animacion.png", 2, 3) + grilla.asignar(actor) + + Entonces, a partir de ahora nuestro actor muestra solamente un + cuadro de toda la grilla. + + Si quieres avanzar la animacion tienes que modificar el objeto + grilla y asignarlo nuevamente al actor:: + + grilla.avanzar() + grilla.asignar(actor) + """ + if not pilas.mundo: + mensaje = "Tiene que invocar a la funcion ``pilas.iniciar()`` para comenzar." + print mensaje + raise Exception(mensaje) + + ruta = pilas.utils.obtener_ruta_al_recurso(ruta) + return pilas.mundo.motor.obtener_grilla(ruta, columnas, filas) + +def cargar_lienzo(): + """Representa un rectangulo (inicialmente transparente) para dibujar.""" + return pilas.mundo.motor.obtener_lienzo() + +def cargar_superficie(ancho, alto): + return pilas.mundo.motor.obtener_superficie(ancho, alto) + +cargar_imagen = cargar diff --git a/pilas/interfaz/__init__.py b/pilas/interfaz/__init__.py new file mode 100644 index 0000000..8c9c1e0 --- /dev/null +++ b/pilas/interfaz/__init__.py @@ -0,0 +1,14 @@ +# -*- encoding: utf-8 -*- +# pilas engine - a video game framework. +# +# copyright 2010 - hugo ruscitti +# license: lgplv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# website - http://www.pilas-engine.com.ar + + +from deslizador import Deslizador +from selector import Selector +from ingreso_de_texto import IngresoDeTexto +from lista_seleccion import ListaSeleccion +from boton import Boton diff --git a/pilas/interfaz/boton.py b/pilas/interfaz/boton.py new file mode 100644 index 0000000..28043d4 --- /dev/null +++ b/pilas/interfaz/boton.py @@ -0,0 +1,65 @@ +# -*- encoding: utf-8 -*- +# pilas engine - a video game framework. +# +# copyright 2010 - hugo ruscitti +# license: lgplv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# website - http://www.pilas-engine.com.ar + +import pilas + +class Boton(pilas.actores.Actor): + + def __init__(self, texto="Sin texto", x=0, y=0, icono=None): + pilas.actores.Actor.__init__(self, x=x, y=y) + self.texto = texto + self._crear_imagenes_de_botones() + self.centro = ("centro", "centro") + self.funcion = None + + if icono: + self.icono = pilas.imagenes.cargar(icono) + else: + self.icono = None + + pilas.eventos.mueve_mouse.conectar(self.cuando_mueve_el_mouse) + pilas.eventos.click_de_mouse.conectar(self.cuando_hace_click) + + def conectar(self, funcion): + self.funcion = funcion + + def _crear_imagenes_de_botones(self): + "Genera las 3 imagenes de los botones." + ancho, alto = pilas.utils.obtener_area_de_texto(self.texto) + tema = pilas.imagenes.cargar("boton/tema.png") + + self.imagen_normal = self._crear_imagen(tema, self.texto, ancho, 0) + self.imagen_sobre = self._crear_imagen(tema, self.texto, ancho, 103) + self.imagen_click = self._crear_imagen(tema, self.texto, ancho, 205) + + self.imagen = self.imagen_normal + + def cuando_mueve_el_mouse(self, evento): + if self.colisiona_con_un_punto(evento.x, evento.y): + self.imagen = self.imagen_sobre + else: + self.imagen = self.imagen_normal + + def cuando_hace_click(self, evento): + if self.imagen == self.imagen_sobre: + self.imagen = self.imagen_click + + if self.funcion: + self.funcion() + + def _crear_imagen(self, tema, texto, ancho, dx): + "Genera una imagen de superficie de boton." + imagen = pilas.imagenes.cargar_superficie(20 + ancho, 30) + imagen.pintar_parte_de_imagen(tema, dx, 0, 5, 25, 0, 0) + + for x in range(1, ancho + 20, 5): + imagen.pintar_parte_de_imagen(tema, dx + 5, 0, 5, 25, x, 0) + + imagen.pintar_parte_de_imagen(tema, dx + 75, 0, 5, 25, ancho + 15, 0) + imagen.texto(texto, 10, 17) + return imagen diff --git a/pilas/interfaz/deslizador.py b/pilas/interfaz/deslizador.py new file mode 100644 index 0000000..37322ef --- /dev/null +++ b/pilas/interfaz/deslizador.py @@ -0,0 +1,87 @@ +# -*- encoding: utf-8 -*- +# pilas engine - a video game framework. +# +# copyright 2010 - hugo ruscitti +# license: lgplv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# website - http://www.pilas-engine.com.ar +# +# Deslizador creado por Pablo Garrido + +import pilas +from pilas.actores import Actor + + +class Deslizador(Actor): + + def __init__(self, x=0, y=0, ruta_barra = 'interfaz/barra.png', + ruta_deslizador = 'interfaz/deslizador.png'): + + self.deslizador = None + Actor.__init__(self, ruta_barra, x=x, y=y) + self.deslizador = Actor(ruta_deslizador, self.x, self.y) + self.centro = ('izquierda', 'centro') + + self.click = False + + pilas.eventos.click_de_mouse.conectar(self.click_del_mouse) + pilas.eventos.mueve_mouse.conectar(self.movimiento_del_mouse) + pilas.eventos.termina_click.conectar(self.termino_del_click) + + self.progreso = 0 + self.posicion_relativa_x = 0 + + self.funciones = [] + + # establecemos posicion inicial + self.x = x + self.y = y + + def set_transparencia(self, nuevo_valor): + self.transparencia = nuevo_valor + self.deslizador.transparencia = nuevo_valor + + def definir_posicion(self, x, y): + self.limite_izq = self.x + self.limite_der = self.x + self.obtener_ancho() + + self._actor.definir_posicion(x, y) + if self.deslizador: + self.deslizador.definir_posicion(x + self.posicion_relativa_x, y) + + def conectar(self, f): + self.funciones.append(f) + + def desconectar(self, f): + self.funciones.remove(f) + + def ejecutar_funciones(self, valor): + for i in self.funciones: + i(valor) + + def click_del_mouse(self, click): + + if self.deslizador.colisiona_con_un_punto(click.x, click.y): + self.click = True + + def movimiento_del_mouse(self, movimiento): + if self.click == True: + ancho = self.obtener_ancho() + factor = (self.deslizador.x + (ancho - abs(self.x))) / ancho - 1 + self.progreso = factor + + self.ejecutar_funciones(factor) + + self.deslizador.x = movimiento.x + + if self.deslizador.x <= self.limite_izq: + self.deslizador.x = self.limite_izq + + elif self.deslizador.x >= self.limite_der: + self.deslizador.x = self.limite_der + + self.posicion_relativa_x = self.deslizador.x - self.x + + + def termino_del_click(self, noclick): + self.click = False diff --git a/pilas/interfaz/ingreso_de_texto.py b/pilas/interfaz/ingreso_de_texto.py new file mode 100644 index 0000000..418c932 --- /dev/null +++ b/pilas/interfaz/ingreso_de_texto.py @@ -0,0 +1,86 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +import pilas +import re + +class IngresoDeTexto(pilas.actores.Actor): + + def __init__(self, texto_inicial="", x=0, y=0, ancho=300, limite_de_caracteres=20, icono=None): + pilas.actores.Actor.__init__(self, x=x, y=y) + self.texto = texto_inicial + self.cursor = "" + self._cargar_lienzo(ancho) + + if icono: + self.icono = pilas.imagenes.cargar(icono) + else: + self.icono = None + + self.imagen_caja = pilas.imagenes.cargar("interfaz/caja.png") + self.centro = ("centro", "centro") + self._actualizar_imagen() + self.limite_de_caracteres = limite_de_caracteres + self.cualquier_caracter() + + pilas.eventos.pulsa_tecla.conectar(self.cuando_pulsa_una_tecla) + pilas.mundo.agregar_tarea_siempre(0.40, self._actualizar_cursor) + + def _actualizar_cursor(self): + if self.cursor == "": + self.cursor = "_" + else: + self.cursor = "" + + self._actualizar_imagen() + return True + + def cualquier_caracter(self): + self.caracteres_permitidos = re.compile(".*") + + def solo_numeros(self): + self.caracteres_permitidos = re.compile("\d+") + + def solo_letras(self): + self.caracteres_permitidos = re.compile("[a-z]+") + + def cuando_pulsa_una_tecla(self, evento): + if evento.codigo == '\x08' or evento.texto == '\x08': + # Indica que se quiere borrar un caracter + self.texto = self.texto[:-1] + else: + if len(self.texto) < self.limite_de_caracteres: + nuevo_texto = self.texto + evento.texto + + if (self.caracteres_permitidos.match(evento.texto)): + self.texto = self.texto + evento.texto + else: + print "Rechazando el ingreso del caracter:", evento.texto + else: + print "Rechazando caracter por llegar al limite." + + self._actualizar_imagen() + + def _cargar_lienzo(self, ancho): + self.imagen = pilas.imagenes.cargar_superficie(ancho, 30) + + def _actualizar_imagen(self): + ancho = self.imagen_caja.ancho() + alto = self.imagen_caja.alto() + self.imagen.pintar_parte_de_imagen(self.imagen_caja, 0, 0, 40, ancho, 0, 0) + + if self.icono: + dx = 20 + self.imagen.pintar_parte_de_imagen(self.icono, 0, 0, 40, ancho, 7, 7) + else: + dx = 0 + + for x in range(40, self.imagen.ancho() - 40): + self.imagen.pintar_parte_de_imagen(self.imagen_caja, ancho - 40, 0, 40, alto, x, 0) + + self.imagen.texto(self.texto + self.cursor, 15 + dx, 20) diff --git a/pilas/interfaz/lista_seleccion.py b/pilas/interfaz/lista_seleccion.py new file mode 100644 index 0000000..4feb9fd --- /dev/null +++ b/pilas/interfaz/lista_seleccion.py @@ -0,0 +1,53 @@ +# -*- encoding: utf-8 -*- +# pilas engine - a video game framework. +# +# copyright 2010 - hugo ruscitti +# license: lgplv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# website - http://www.pilas-engine.com.ar + +import pilas +from pilas.actores import Actor + +class ListaSeleccion(Actor): + + def __init__(self, opciones, funcion_a_ejecutar=None, x=0, y=0): + Actor.__init__(self, x=x, y=y) + self.opciones = opciones + self.funcion_a_ejecutar = funcion_a_ejecutar + + ancho, alto = pilas.mundo.motor.obtener_area_de_texto("\n".join(opciones)) + self.imagen = pilas.imagenes.cargar_superficie(int(ancho + 35), int(alto + 5)) + + self._pintar_opciones() + + pilas.eventos.mueve_mouse.conectar(self.cuando_mueve_el_mouse) + pilas.eventos.click_de_mouse.conectar(self.cuando_hace_click_con_el_mouse) + self.centro = ("centro", "centro") + + def _pintar_opciones(self, pinta_indice_opcion=None): + self.imagen.pintar(pilas.colores.blanco) + + if pinta_indice_opcion != None: + self.imagen.rectangulo(0, pinta_indice_opcion * 19, self.imagen.ancho(), 17, relleno=True, color=pilas.colores.naranja) + + for indice, opcion in enumerate(self.opciones): + self.imagen.texto(opcion, 15, y=12 + indice * 20, color=pilas.colores.negro) + + def cuando_mueve_el_mouse(self, evento): + if self.colisiona_con_un_punto(evento.x, evento.y): + opcion_seleccionada = self._detectar_opcion_bajo_el_mouse(evento) + self._pintar_opciones(opcion_seleccionada) + + def cuando_hace_click_con_el_mouse(self, evento): + if self.colisiona_con_un_punto(evento.x, evento.y): + opcion = self._detectar_opcion_bajo_el_mouse(evento) + if self.funcion_a_ejecutar: + self.funcion_a_ejecutar(self.opciones[opcion]) + else: + print "Cuidado, no has definido funcion a ejecutar en la lista de seleccion." + + def _detectar_opcion_bajo_el_mouse(self, evento): + opcion = int((self.arriba - evento.y ) / 20) + if opcion in range(0, len(self.opciones)): + return opcion diff --git a/pilas/interfaz/selector.py b/pilas/interfaz/selector.py new file mode 100644 index 0000000..3d15663 --- /dev/null +++ b/pilas/interfaz/selector.py @@ -0,0 +1,64 @@ +# -*- encoding: utf-8 -*- +# pilas engine - a video game framework. +# +# copyright 2010 - hugo ruscitti +# license: lgplv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# website - http://www.pilas-engine.com.ar +# +# Deslizador creado por Pablo Garrido + +import pilas + +class Selector(pilas.actores.Actor): + + def __init__(self, texto, x=0, y=0, ancho=200): + pilas.actores.Actor.__init__(self, x=x, y=y) + + self.texto = texto + self._cargar_lienzo(ancho) + self._cargar_imagenes() + self.funcion_de_respuesta = None + + self.deseleccionar() + pilas.eventos.click_de_mouse.conectar(self.detection_click_mouse) + + def _cargar_imagenes(self): + self.imagen_selector = pilas.imagenes.cargar("interfaz/selector.png") + self.imagen_selector_seleccionado = pilas.imagenes.cargar("interfaz/selector_seleccionado.png") + + def _cargar_lienzo(self, ancho): + self.imagen = pilas.imagenes.cargar_superficie(ancho, 29) + + def pintar_texto(self): + self.imagen.texto(self.texto, 35, 20) + + def deseleccionar(self): + self.seleccionado = False + self.imagen.limpiar() + self.imagen.pintar_imagen(self.imagen_selector) + self.pintar_texto() + self.centro = ("centro", "centro") + + def seleccionar(self): + self.seleccionado = True + self.imagen.limpiar() + self.imagen.pintar_imagen(self.imagen_selector_seleccionado) + self.pintar_texto() + self.centro = ("centro", "centro") + + def detection_click_mouse(self, click): + if self.colisiona_con_un_punto(click.x, click.y): + self.alternar_seleccion() + + def alternar_seleccion(self): + if self.seleccionado: + self.deseleccionar() + else: + self.seleccionar() + + if self.funcion_de_respuesta: + self.funcion_de_respuesta(self.seleccionado) + + def definir_accion(self, funcion): + self.funcion_de_respuesta = funcion diff --git a/pilas/interpolaciones.py b/pilas/interpolaciones.py new file mode 100644 index 0000000..4f52973 --- /dev/null +++ b/pilas/interpolaciones.py @@ -0,0 +1,86 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +class Interpolacion(object): + """Representa una interpolacion, que pasa por varios puntos clave. + + Las interpolacione se utilizan para realizar movimientos de + actores en la pantalla. O simplemente para cambiar el + estado de un actor de un punto a otro, por ejemplo, de 0 a 360 + grados de manera gradual. + + Todo objeto de interpolaciones se puede asignar directamente a + una propiedad de un actor. Por ejemplo: + + actor.rotation = pilas.interpolations.Lineal(400) + + note que hay un atajo para usar estos objetos, es mejor + utilizar directamente una sentencias como la que sigue:: + + actor.rotation = pilas.interpolate(360) + """ + pass + + +class Lineal(Interpolacion): + "Representa una interpolación lineal." + + def __init__(self, values, duration, delay): + """Inicializa la interpolación. + + ``values`` tiene que ser una lista con todos los puntos + por los que se quiere adoptar valores y ``duration`` es la cantidad + de segundos que deben tomarse para realizar la interpolación. + """ + self.values = values + self.duration = duration + self.delay = delay + + def __neg__(self): + "Retorna la interpolación inversa a la original." + new_values = list(self.values) + new_values.reverse() + return Lineal(new_values, self.duration, self.delay) + + def apply(self, target, function): + """Aplica la interpolación a un actor usando un método. + + Esta funcionalidad se utiliza para que toda interpolación + se pueda acoplar a un actor. + + La idea es contar con la interpolación, un actor y luego + ponerla en funcionamiento:: + + mi_interpolacion.apply(mono, set_rotation) + + de esta forma los dos objetos están y seguirán estando + desacoplados.""" + + import pilas + + # Tiempo que se debe invertir para hacer cada interpolacion + # individual. + step = self.duration / float(len(self.values)) + step *= 1000.0 + + # En base a la funcion busca el getter que le dara + # el valor inicial. + getter = function.replace('set_', 'get_') + function_to_get_value = getattr(target, getter) + fist_value = function_to_get_value() + + # Le indica al objeto que tiene que hacer para cumplir + # con cada paso de la interpolacion. + for index, value in enumerate(self.values): + pilas.mundo.tweener.addTweenNoArgs(target, function=function, + initial_value=fist_value, + value=value, + tweenDelay=self.delay * 1000.0 + (index * step), + tweenTime=step) + # El siguiente valor inicial sera el que ha alcanzado. + fist_value = value diff --git a/pilas/lienzo.py b/pilas/lienzo.py new file mode 100644 index 0000000..5ed9bdd --- /dev/null +++ b/pilas/lienzo.py @@ -0,0 +1,6 @@ +from pilas.actores.pizarra import PizarraAbstracta + +# TODO: eliminar la pizarraAbstacta y reemplazarla +# por este lienzo. + +Lienzo = PizarraAbstracta \ No newline at end of file diff --git a/pilas/motores/__init__.py b/pilas/motores/__init__.py new file mode 100644 index 0000000..57d6284 --- /dev/null +++ b/pilas/motores/__init__.py @@ -0,0 +1,9 @@ +# -*- encoding: utf-8 -*- +# pilas engine - a video game framework. +# +# copyright 2010 - hugo ruscitti +# license: lgplv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# website - http://www.pilas-engine.com.ar + +import motor diff --git a/pilas/motores/motor.py b/pilas/motores/motor.py new file mode 100644 index 0000000..76c1d26 --- /dev/null +++ b/pilas/motores/motor.py @@ -0,0 +1,75 @@ +# pilas engine - a video game framework. +# +# copyright 2010 - hugo ruscitti +# license: lgplv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# website - http://www.pilas-engine.com.ar + + +def abstract(): + raise Exception("Tienes que re-definir este metodo.") + +class Motor(object): + + def __init__(self): + pass + + def obtener_actor(self, imagen, x, y): + abstract() + + def obtener_texto(self, texto, x, y): + abstract() + + def obtener_canvas(self, ancho, alto): + abstract() + + def obtener_grilla(self, ruta, columnas, filas): + abstract() + + def crear_ventana(self, ancho, alto, titulo): + abstract() + + def ocultar_puntero_del_mouse(self): + abstract() + + def mostrar_puntero_del_mouse(self): + abstract() + + def cerrar_ventana(self): + abstract() + + def dibujar_circulo(self, x, y, radio, color, color_borde): + abstract() + + def pulsa_tecla(self, tecla): + abstract() + + def centrar_ventana(self): + abstract() + + def procesar_y_emitir_eventos(self): + abstract() + + def procesar_evento_teclado(self, event): + abstract() + + def definir_centro_de_la_camara(self, x, y): + abstract() + + def obtener_centro_de_la_camara(self): + abstract() + + def pintar(self, color): + abstract() + + def cargar_sonido(self, ruta): + abstract() + + def cargar_imagen(self, ruta): + abstract() + + def obtener_imagen_cairo(self, imagen): + abstract() + + def ejecutar_bucle_principal(self, mundo, ignorar_errores): + abstract() diff --git a/pilas/motores/motor_qt.py b/pilas/motores/motor_qt.py new file mode 100644 index 0000000..b416031 --- /dev/null +++ b/pilas/motores/motor_qt.py @@ -0,0 +1,705 @@ +# -*- encoding: utf-8 -*- +# pilas engine - a video game framework. +# +# copyright 2010 - hugo ruscitti +# license: lgplv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# website - http://www.pilas-engine.com.ar + +import os +import sys +import copy +from PyQt4 import QtGui, QtCore +from PyQt4.QtGui import QWidget + +try: + from PyQt4 import QtOpenGL + from PyQt4.QtOpenGL import QGLWidget +except ImportError: + QGLWidget = object + print "No se encuentra soporte OpenGL en este equipo." + + +import motor +from pilas import imagenes +from pilas import actores +from pilas import eventos +from pilas import utils +from pilas import depurador + +from pilas import fps +from pilas import simbolos +from pilas import colores + + +class BaseActor(object): + + def __init__(self): + self._rotacion = 0 + self._transparencia = 0 + self.centro_x = 0 + self.centro_y = 0 + self._escala_x = 1 + self._escala_y = 1 + self._espejado = False + self.fijo = 0 + + def definir_centro(self, x, y): + self.centro_x = x + self.centro_y = y + + def obtener_posicion(self): + return self.x, self.y + + def definir_posicion(self, x, y): + self.x, self.y = x, y + + def obtener_escala(self): + return self._escala_x + + def definir_escala(self, s): + self._escala_x = s + self._escala_y = s + + def definir_escala_x(self, s): + self._escala_x = s + + def definir_escala_y(self, s): + self._escala_y = s + + def definir_transparencia(self, nuevo_valor): + self._transparencia = nuevo_valor + + def obtener_transparencia(self): + return self._transparencia + + def obtener_rotacion(self): + return self._rotacion + + def definir_rotacion(self, r): + self._rotacion = r + + def set_espejado(self, espejado): + self._espejado = espejado + +class QtImagen(object): + + def __init__(self, ruta): + self.ruta_original = ruta + self._imagen = QtGui.QPixmap(ruta) + + def ancho(self): + return self._imagen.size().width() + + def alto(self): + return self._imagen.size().height() + + def centro(self): + "Retorna una tupla con la coordenada del punto medio del la imagen." + return (self.ancho()/2, self.alto()/2) + + def avanzar(self): + pass + + def dibujar(self, motor, x, y, dx=0, dy=0, escala_x=1, escala_y=1, rotacion=0, transparencia=0): + """Dibuja la imagen sobre la ventana que muestra el motor. + + x, y: indican la posicion dentro del mundo. + dx, dy: es el punto centro de la imagen (importante para rotaciones). + escala_x, escala_yindican cambio de tamano (1 significa normal). + rotacion: angulo de inclinacion en sentido de las agujas del reloj. + """ + + motor.canvas.save() + centro_x, centro_y = motor.centro_fisico() + motor.canvas.translate(x + centro_x, centro_y - y) + motor.canvas.rotate(rotacion) + motor.canvas.scale(escala_x, escala_y) + + if transparencia: + motor.canvas.setOpacity(1 - transparencia/100.0) + + self._dibujar_pixmap(motor, -dx, -dy) + motor.canvas.restore() + + def _dibujar_pixmap(self, motor, x, y): + motor.canvas.drawPixmap(x, y, self._imagen) + + def __str__(self): + nombre_imagen = os.path.basename(self.ruta_original) + return "" %(nombre_imagen) + + +class QtGrilla(QtImagen): + + """Representa una grilla regular, que se utiliza en animaciones. + + La grilla regular se tiene que crear indicando la cantidad + de filas y columnas. Una vez definida se puede usar como + una imagen normal, solo que tiene dos metodos adicionales + para ``definir_cuadro`` y ``avanzar`` el cuadro actual. + """ + + def __init__(self, ruta, columnas=1, filas=1): + QtImagen.__init__(self, ruta) + self.cantidad_de_cuadros = columnas * filas + self.columnas = columnas + self.filas = filas + self.cuadro_ancho = QtImagen.ancho(self) / columnas + self.cuadro_alto = QtImagen.alto(self) / filas + self.definir_cuadro(0) + + def ancho(self): + return self.cuadro_ancho + + def alto(self): + return self.cuadro_alto + + def _dibujar_pixmap(self, motor, x, y): + motor.canvas.drawPixmap(x, y, self._imagen, self.dx, self.dy, + self.cuadro_ancho, self.cuadro_alto) + + def definir_cuadro(self, cuadro): + self._cuadro = cuadro + + frame_col = cuadro % self.columnas + frame_row = cuadro / self.columnas + + self.dx = frame_col * self.cuadro_ancho + self.dy = frame_row * self.cuadro_alto + + def avanzar(self): + ha_reiniciado = False + cuadro_actual = self._cuadro + 1 + + if cuadro_actual >= self.cantidad_de_cuadros: + cuadro_actual = 0 + ha_reiniciado = True + + self.definir_cuadro(cuadro_actual) + return ha_reiniciado + + def obtener_cuadro(self): + return self._cuadro + + def dibujarse_sobre_una_pizarra(self, pizarra, x, y): + pizarra.pintar_parte_de_imagen(self, self.dx, self.dy, self.cuadro_ancho, self.cuadro_alto, x, y) + +class QtTexto(QtImagen): + + def __init__(self, texto, magnitud, motor): + self._ancho, self._alto = motor.obtener_area_de_texto(texto, magnitud) + + def _dibujar_pixmap(self, motor, dx, dy): + nombre_de_fuente = motor.canvas.font().family() + fuente = QtGui.QFont(nombre_de_fuente, self.magnitud) + metrica = QtGui.QFontMetrics(fuente) + + r, g, b, a = self.color.obtener_componentes() + motor.canvas.setPen(QtGui.QColor(r, g, b)) + motor.canvas.setFont(fuente) + lines = self.texto.split('\n') + + for line in lines: + motor.canvas.drawText(dx, dy + self._alto, line) + dy += metrica.height() + + def ancho(self): + return self._ancho + + def alto(self): + return self._alto + + +class QtLienzo(QtImagen): + + def __init__(self): + pass + + def texto(self, motor, cadena, x=0, y=0, magnitud=10, fuente=None, color=colores.negro): + "Imprime un texto respespetando el desplazamiento de la camara." + self.texto_absoluto(motor, cadena, x, y, magnitud, fuente, color) + + def texto_absoluto(self, motor, cadena, x=0, y=0, magnitud=10, fuente=None, color=colores.negro): + "Imprime un texto sin respetar al camara." + x, y = utils.hacer_coordenada_pantalla_absoluta(x, y) + + r, g, b, a = color.obtener_componentes() + motor.canvas.setPen(QtGui.QColor(r, g, b)) + + if not fuente: + fuente = motor.canvas.font().family() + + motor.canvas.setFont(QtGui.QFont(fuente, magnitud)) + motor.canvas.drawText(x, y, cadena) + + def pintar(self, motor, color): + r, g, b, a = color.obtener_componentes() + ancho, alto = motor.obtener_area() + motor.canvas.fillRect(0, 0, ancho, alto, QtGui.QColor(r, g, b)) + + def linea(self, motor, x0, y0, x1, y1, color=colores.negro, grosor=1): + x0, y0 = utils.hacer_coordenada_pantalla_absoluta(x0, y0) + x1, y1 = utils.hacer_coordenada_pantalla_absoluta(x1, y1) + + r, g, b, a = color.obtener_componentes() + color = QtGui.QColor(r, g, b) + pen = QtGui.QPen(color, grosor) + motor.canvas.setPen(pen) + motor.canvas.drawLine(x0, y0, x1, y1) + + def poligono(self, motor, puntos, color=colores.negro, grosor=1, cerrado=False): + x, y = puntos[0] + if cerrado: + puntos.append((x, y)) + + for p in puntos[1:]: + nuevo_x, nuevo_y = p + self.linea(motor, x, y, nuevo_x, nuevo_y, color, grosor) + x, y = nuevo_x, nuevo_y + + + def cruz(self, motor, x, y, color=colores.negro, grosor=1): + t = 3 + self.linea(motor, x - t, y - t, x + t, y + t, color, grosor) + self.linea(motor, x + t, y - t, x - t, y + t, color, grosor) + + def circulo(self, motor, x, y, radio, color=colores.negro, grosor=1): + x, y = utils.hacer_coordenada_pantalla_absoluta(x, y) + + r, g, b, a = color.obtener_componentes() + color = QtGui.QColor(r, g, b) + pen = QtGui.QPen(color, grosor) + motor.canvas.setPen(pen) + motor.canvas.drawEllipse(x -radio, y-radio, radio*2, radio*2) + + def rectangulo(self, motor, x, y, ancho, alto, color=colores.negro, grosor=1): + x, y = utils.hacer_coordenada_pantalla_absoluta(x, y) + + r, g, b, a = color.obtener_componentes() + color = QtGui.QColor(r, g, b) + pen = QtGui.QPen(color, grosor) + motor.canvas.setPen(pen) + motor.canvas.drawRect(x, y, ancho, alto) + +class QtSuperficie(QtImagen): + + def __init__(self, ancho, alto): + self._imagen = QtGui.QPixmap(ancho, alto) + self._imagen.fill(QtGui.QColor(255, 255, 255, 0)) + self.canvas = QtGui.QPainter() + + def pintar(self, color): + r, g, b, a = color.obtener_componentes() + self._imagen.fill(QtGui.QColor(r, g, b, a)) + + def pintar_parte_de_imagen(self, imagen, origen_x, origen_y, ancho, alto, x, y): + self.canvas.begin(self._imagen) + self.canvas.drawPixmap(x, y, imagen._imagen, origen_x, origen_y, ancho, alto) + self.canvas.end() + + def pintar_imagen(self, imagen, x=0, y=0): + self.pintar_parte_de_imagen(imagen, 0, 0, imagen.ancho(), imagen.alto(), x, y) + + def texto(self, cadena, x=0, y=0, magnitud=10, fuente=None, color=colores.negro): + self.canvas.begin(self._imagen) + r, g, b, a = color.obtener_componentes() + self.canvas.setPen(QtGui.QColor(r, g, b)) + dx = x + dy = y + + if not fuente: + fuente = self.canvas.font().family() + + font = QtGui.QFont(fuente, magnitud) + self.canvas.setFont(font) + metrica = QtGui.QFontMetrics(font) + + for line in cadena.split('\n'): + self.canvas.drawText(dx, dy, line) + dy += metrica.height() + + self.canvas.end() + + def circulo(self, x, y, radio, color=colores.negro, relleno=False, grosor=1): + self.canvas.begin(self._imagen) + + r, g, b, a = color.obtener_componentes() + color = QtGui.QColor(r, g, b) + pen = QtGui.QPen(color, grosor) + self.canvas.setPen(pen) + + if relleno: + self.canvas.setBrush(color) + + self.canvas.drawEllipse(x -radio, y-radio, radio*2, radio*2) + self.canvas.end() + + def rectangulo(self, x, y, ancho, alto, color=colores.negro, relleno=False, grosor=1): + self.canvas.begin(self._imagen) + + r, g, b, a = color.obtener_componentes() + color = QtGui.QColor(r, g, b) + pen = QtGui.QPen(color, grosor) + self.canvas.setPen(pen) + + if relleno: + self.canvas.setBrush(color) + + self.canvas.drawRect(x, y, ancho, alto) + self.canvas.end() + + def linea(self, x, y, x2, y2, color=colores.negro, grosor=1): + self.canvas.begin(self._imagen) + + r, g, b, a = color.obtener_componentes() + color = QtGui.QColor(r, g, b) + pen = QtGui.QPen(color, grosor) + self.canvas.setPen(pen) + + self.canvas.drawLine(x, y, x2, y2) + self.canvas.end() + + def poligono(self, puntos, color, grosor, cerrado=False): + x, y = puntos[0] + + if cerrado: + puntos.append((x, y)) + + for p in puntos[1:]: + nuevo_x, nuevo_y = p + self.linea(x, y, nuevo_x, nuevo_y, color, grosor) + x, y = nuevo_x, nuevo_y + + def dibujar_punto(self, x, y, color=colores.negro): + self.circulo(x, y, 3, color=color, relleno=True) + + def limpiar(self): + self._imagen.fill(QtGui.QColor(0, 0, 0, 0)) + +class QtActor(BaseActor): + + def __init__(self, imagen="sin_imagen.png", x=0, y=0): + + if isinstance(imagen, str): + self.imagen = imagenes.cargar(imagen) + else: + self.imagen = imagen + + self.x = x + self.y = y + BaseActor.__init__(self) + + def definir_imagen(self, imagen): + # permite que varios actores usen la misma grilla. + if isinstance(imagen, QtGrilla): + self.imagen = copy.copy(imagen) + else: + self.imagen = imagen + + def obtener_imagen(self): + return self.imagen + + def dibujar(self, motor): + escala_x, escala_y = self._escala_x, self._escala_y + + if self._espejado: + escala_x *= -1 + + if not self.fijo: + x = self.x - motor.camara_x + y = self.y - motor.camara_y + else: + x = self.x + y = self.y + + self.imagen.dibujar(motor, x, y, + self.centro_x, self.centro_y, + escala_x, escala_y, self._rotacion, self._transparencia) + +class QtSonido: + + def __init__(self, ruta): + import pygame + pygame.mixer.init() + pygame.mixer.init() + self.sonido = pygame.mixer.Sound(ruta) + + def reproducir(self): + # TODO: quitar esta nota... + # print "Usando pygame para reproducir sonido" + self.sonido.play() + +class QtBase(motor.Motor): + + #app = QtGui.QApplication([]) + + def __init__(self): + motor.Motor.__init__(self) + self.canvas = QtGui.QPainter() + self.setMouseTracking(True) + self.fps = fps.FPS(60, True) + self.pausa_habilitada = False + self.depurador = depurador.Depurador(self.obtener_lienzo(), self.fps) + self.mouse_x = 0 + self.mouse_y = 0 + self.camara_x = 0 + self.camara_y = 0 + + def iniciar_ventana(self, ancho, alto, titulo, pantalla_completa): + self.ancho = ancho + self.alto = alto + self.ancho_original = ancho + self.alto_original = alto + self.titulo = titulo + self.centrar_ventana() + self.setWindowTitle(self.titulo) + + if pantalla_completa: + self.showFullScreen() + else: + self.show() + + # Activa la invocacion al evento timerEvent. + self.startTimer(1000/60.0) + + def pantalla_completa(self): + self.showFullScreen() + + def pantalla_modo_ventana(self): + self.showNormal() + + def esta_en_pantalla_completa(self): + return self.isFullScreen() + + def alternar_pantalla_completa(self): + """Permite cambiar el modo de video. + + Si está en modo ventana, pasa a pantalla completa y viceversa. + """ + if self.esta_en_pantalla_completa(): + self.pantalla_modo_ventana() + else: + self.pantalla_completa() + + def centro_fisico(self): + "Centro de la ventana para situar el punto (0, 0)" + return self.ancho_original/2, self.alto_original/2 + + def obtener_area(self): + return (self.ancho_original, self.alto_original) + + def centrar_ventana(self): + escritorio = QtGui.QDesktopWidget().screenGeometry() + self.setGeometry( + (escritorio.width()-self.ancho)/2, + (escritorio.height()-self.alto)/2, self.ancho, self.alto) + + def obtener_actor(self, imagen, x, y): + return QtActor(imagen, x, y) + + def obtener_texto(self, texto, magnitud): + return QtTexto(texto, magnitud, self) + + def obtener_grilla(self, ruta, columnas, filas): + return QtGrilla(ruta, columnas, filas) + + def actualizar_pantalla(self): + self.ventana.update() + + def definir_centro_de_la_camara(self, x, y): + self.camara_x = x + self.camara_y = y + + def obtener_centro_de_la_camara(self): + return (self.camara_x, self.camara_y) + + def cargar_sonido(self, ruta): + return QtSonido(ruta) + + def cargar_imagen(self, ruta): + return QtImagen(ruta) + + def obtener_lienzo(self): + return QtLienzo() + + def obtener_superficie(self, ancho, alto): + return QtSuperficie(ancho, alto) + + def ejecutar_bucle_principal(self, mundo, ignorar_errores): + #sys.exit(self.app.exec_()) + pass + + def paintEvent(self, event): + self.canvas.begin(self) + + self.canvas.setClipping(True) + self.canvas.setClipRect(0, 0, self.alto * self.ancho_original / self.alto_original, self.alto) + + alto = self.alto / float(self.alto_original) + self.canvas.scale(alto, alto) + + + self.canvas.setRenderHint(QtGui.QPainter.HighQualityAntialiasing, False) + self.canvas.setRenderHint(QtGui.QPainter.SmoothPixmapTransform, True) + self.canvas.setRenderHint(QtGui.QPainter.Antialiasing, False) + + self.depurador.comienza_dibujado(self) + + for actor in actores.todos: + try: + actor.dibujar(self) + except Exception as e: + print e + actor.eliminar() + + self.depurador.dibuja_al_actor(self, actor) + + self.depurador.termina_dibujado(self) + self.canvas.end() + + def timerEvent(self, event): + + if not self.pausa_habilitada: + try: + self.realizar_actualizacion_logica() + except Exception as e: + print e + + # Invoca el dibujado de la pantalla. + self.update() + + + def realizar_actualizacion_logica(self): + for x in range(self.fps.actualizar()): + if not self.pausa_habilitada: + eventos.actualizar.send("Qt::timerEvent") + + for actor in actores.todos: + actor.pre_actualizar() + actor.actualizar() + + def resizeEvent(self, event): + self.ancho = event.size().width() + self.alto = event.size().height() + + def mousePressEvent(self, e): + escala = self.escala() + x, y = utils.convertir_de_posicion_fisica_relativa(e.pos().x()/escala, e.pos().y()/escala) + eventos.click_de_mouse.send("Qt::mousePressEvent", x=x, y=y, dx=0, dy=0) + + def mouseReleaseEvent(self, e): + escala = self.escala() + x, y = utils.convertir_de_posicion_fisica_relativa(e.pos().x()/escala, e.pos().y()/escala) + eventos.termina_click.send("Qt::mouseReleaseEvent", x=x, y=y, dx=0, dy=0) + + def wheelEvent(self, e): + eventos.mueve_rueda.send("ejecutar", delta=e.delta() / 120) + + def mouseMoveEvent(self, e): + escala = self.escala() + x, y = utils.convertir_de_posicion_fisica_relativa(e.pos().x()/escala, e.pos().y()/escala) + dx, dy = x - self.mouse_x, y - self.mouse_y + eventos.mueve_mouse.send("Qt::mouseMoveEvent", x=x, y=y, dx=dx, dy=dy) + self.mouse_x = x + self.mouse_y = y + + def keyPressEvent(self, event): + codigo_de_tecla = self.obtener_codigo_de_tecla_normalizado(event.key()) + + if event.key() == QtCore.Qt.Key_Escape: + eventos.pulsa_tecla_escape.send("Qt::keyPressEvent") + if event.key() == QtCore.Qt.Key_P: + self.alternar_pausa() + if event.key() == QtCore.Qt.Key_F: + self.alternar_pantalla_completa() + + eventos.pulsa_tecla.send("Qt::keyPressEvent", codigo=codigo_de_tecla, texto=event.text()) + + def keyReleaseEvent(self, event): + codigo_de_tecla = self.obtener_codigo_de_tecla_normalizado(event.key()) + eventos.suelta_tecla.send("Qt::keyReleaseEvent", codigo=codigo_de_tecla, texto=event.text()) + + def obtener_codigo_de_tecla_normalizado(self, tecla_qt): + teclas = { + QtCore.Qt.Key_Left: simbolos.IZQUIERDA, + QtCore.Qt.Key_Right: simbolos.DERECHA, + QtCore.Qt.Key_Up: simbolos.ARRIBA, + QtCore.Qt.Key_Down: simbolos.ABAJO, + QtCore.Qt.Key_Space: simbolos.SELECCION, + QtCore.Qt.Key_Return: simbolos.SELECCION, + QtCore.Qt.Key_F1: simbolos.F1, + QtCore.Qt.Key_F2: simbolos.F2, + QtCore.Qt.Key_F3: simbolos.F3, + QtCore.Qt.Key_F4: simbolos.F4, + QtCore.Qt.Key_F5: simbolos.F5, + QtCore.Qt.Key_F6: simbolos.F6, + QtCore.Qt.Key_F7: simbolos.F7, + QtCore.Qt.Key_F8: simbolos.F8, + QtCore.Qt.Key_F9: simbolos.F9, + QtCore.Qt.Key_F10: simbolos.F10, + QtCore.Qt.Key_F11: simbolos.F11, + QtCore.Qt.Key_F12: simbolos.F12, + } + + if teclas.has_key(tecla_qt): + return teclas[tecla_qt] + else: + return tecla_qt + + def escala(self): + "Obtiene la proporcion de cambio de escala de la pantalla" + return self.alto / float(self.alto_original) + + def obtener_area_de_texto(self, texto, magnitud=10): + ancho = 0 + alto = 0 + + fuente = QtGui.QFont() + fuente.setPointSize(magnitud) + metrica = QtGui.QFontMetrics(fuente) + + lineas = texto.split('\n') + + for linea in lineas: + ancho = max(ancho, metrica.width(linea)) + alto += metrica.height() + + return ancho, alto + + def alternar_pausa(self): + if self.pausa_habilitada: + self.pausa_habilitada = False + self.actor_pausa.eliminar() + else: + self.pausa_habilitada = True + self.actor_pausa = actores.Pausa() + + def ocultar_puntero_del_mouse(self): + bitmap = QtGui.QBitmap(1, 1) + nuevo_cursor = QtGui.QCursor(bitmap, bitmap) + self.setCursor(QtGui.QCursor(nuevo_cursor)) + +class Qt(QtBase, QWidget): + + def __init__(self): + QWidget.__init__(self) + QtBase.__init__(self) + +class QtGL(QtBase, QGLWidget): + + def __init__(self): + if not QGLWidget: + print "Lo siento, OpenGL no esta disponible..." + + QGLWidget.__init__(self) + QtBase.__init__(self) + self._pintar_fondo_negro() + + def _pintar_fondo_negro(self): + color = QtGui.QColor(99, 0, 0) + self.setStyleSheet("QWidget { background-color: %s }" % color.name()) + + +if QGLWidget == object: + QtGL = Qt diff --git a/pilas/mundo.py b/pilas/mundo.py new file mode 100644 index 0000000..ab0de59 --- /dev/null +++ b/pilas/mundo.py @@ -0,0 +1,74 @@ +# -*- encoding: utf-8 -*- +# pilas engine - a video game framework. +# +# copyright 2010 - hugo ruscitti +# license: lgplv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# website - http://www.pilas-engine.com.ar + +import pytweener +from pilas import eventos +from pilas import tareas +from pilas import control +from pilas import fisica +from pilas import escenas +from pilas import colisiones +from pilas import camara + + +class Mundo(object): + """Representa un objeto unico que mantiene en funcionamiento al motor. + + Mundo tiene como responsabilidad iniciar los componentes del + motor y mantener el bucle de juego. + """ + + def __init__(self, motor, ancho, alto, titulo, fps=60, economico=True, + gravedad=(0, -90), pantalla_completa=False): + self.motor = motor + self.motor.iniciar_ventana(ancho, alto, titulo, pantalla_completa) + + self.tweener = pytweener.Tweener() + self.tareas = tareas.Tareas() + self.control = control.Control() + self.colisiones = colisiones.Colisiones() + self.camara = camara.Camara(self) + + eventos.actualizar.conectar(self.actualizar_simuladores) + self.fisica = fisica.Fisica(motor.obtener_area(), gravedad=gravedad) + self.escena_actual = None + + def reiniciar(self): + self.fisica.reiniciar() + + def actualizar_simuladores(self, evento): + self.tweener.update(16) + self.tareas.actualizar(1/60.0) + self.fisica.actualizar() + self.colisiones.verificar_colisiones() + + def terminar(self): + import sys + sys.exit(0) + + def ejecutar_bucle_principal(self, ignorar_errores=False): + "Mantiene en funcionamiento el motor completo." + self.motor.ejecutar_bucle_principal(self, ignorar_errores) + + def definir_escena(self, escena_nueva): + "Cambia la escena que se muestra en pantalla" + + if self.escena_actual: + self.escena_actual.terminar() + + self.escena_actual = escena_nueva + escena_nueva.iniciar() + + def agregar_tarea_una_vez(self, time_out, function, *params): + return self.tareas.una_vez(time_out, function, params) + + def agregar_tarea_siempre(self, time_out, function, *params): + return self.tareas.siempre(time_out, function, params) + + def agregar_tarea(self, time_out, funcion, *parametros): + return self.tareas.condicional(time_out, funcion, parametros) diff --git a/pilas/pilasversion.py b/pilas/pilasversion.py new file mode 100644 index 0000000..47f7053 --- /dev/null +++ b/pilas/pilasversion.py @@ -0,0 +1 @@ +VERSION = '0.59' diff --git a/pilas/pytweener.py b/pilas/pytweener.py new file mode 100644 index 0000000..ff66a62 --- /dev/null +++ b/pilas/pytweener.py @@ -0,0 +1,739 @@ +# pyTweener +# +# Tweening functions for python +# +# Heavily based on caurina Tweener: http://code.google.com/p/tweener/ +# +# Released under M.I.T License - see above url +# Python version by Ben Harling 2009 +import math + +class Tweener(object): + def __init__(self, duration = 0.5, tween = None): + """Tweener + This class manages all active tweens, and provides a factory for + creating and spawning tween motions.""" + self.currentTweens = [] + self.defaultTweenType = tween or Easing.Linear.easeNone + self.defaultDuration = duration or 1.0 + + def hasTweens(self): + return len(self.currentTweens) > 0 + + def addTweenNoArgs(self, obj, function, initial_value, value, **kwargs): + "Similar a addTween, solo que se especifica la funcion y el valor de forma explicita." + args = {function: value, 'initial_value': initial_value} + + if "tweenTime" in kwargs: + t_time = kwargs.pop("tweenTime") + else: t_time = self.defaultDuration + + if "tweenType" in kwargs: + t_type = kwargs.pop("tweenType") + else: t_type = self.defaultTweenType + + if "onCompleteFunction" in kwargs: + t_completeFunc = kwargs.pop("onCompleteFunction") + else: t_completeFunc = None + + if "onUpdateFunction" in kwargs: + t_updateFunc = kwargs.pop("onUpdateFunction") + else: t_updateFunc = None + + if "tweenDelay" in kwargs: + t_delay = kwargs.pop("tweenDelay") + else: t_delay = 0 + + if kwargs: + raise ValueError("No puede llamar a esta funcion con argumentos nombrados, use addTween en su lugar.") + + tw = Tween(obj, t_time, t_type, t_completeFunc, t_updateFunc, t_delay, **args) + if tw: + self.currentTweens.append( tw ) + return tw + + def addTween(self, obj, **kwargs): + """ addTween( object, **kwargs) -> tweenObject or False + + Example: + tweener.addTween( myRocket, throttle=50, setThrust=400, tweenTime=5.0, tweenType=tweener.OUT_QUAD ) + + You must first specify an object, and at least one property or function with a corresponding + change value. The tween will throw an error if you specify an attribute the object does + not possess. Also the data types of the change and the initial value of the tweened item + must match. If you specify a 'set' -type function, the tweener will attempt to get the + starting value by call the corresponding 'get' function on the object. If you specify a + property, the tweener will read the current state as the starting value. You add both + functions and property changes to the same tween. + + in addition to any properties you specify on the object, these keywords do additional + setup of the tween. + + tweenTime = the duration of the motion + tweenType = one of the predefined tweening equations or your own function + onCompleteFunction = specify a function to call on completion of the tween + onUpdateFunction = specify a function to call every time the tween updates + tweenDelay = specify a delay before starting. + """ + if "tweenTime" in kwargs: + t_time = kwargs.pop("tweenTime") + else: t_time = self.defaultDuration + + if "tweenType" in kwargs: + t_type = kwargs.pop("tweenType") + else: t_type = self.defaultTweenType + + if "onCompleteFunction" in kwargs: + t_completeFunc = kwargs.pop("onCompleteFunction") + else: t_completeFunc = None + + if "onUpdateFunction" in kwargs: + t_updateFunc = kwargs.pop("onUpdateFunction") + else: t_updateFunc = None + + if "tweenDelay" in kwargs: + t_delay = kwargs.pop("tweenDelay") + else: t_delay = 0 + + tw = Tween( obj, t_time, t_type, t_completeFunc, t_updateFunc, t_delay, **kwargs ) + if tw: + self.currentTweens.append( tw ) + return tw + + def removeTween(self, tweenObj): + if tweenObj in self.currentTweens: + tweenObj.complete = True + #self.currentTweens.remove( tweenObj ) + + def getTweensAffectingObject(self, obj): + """Get a list of all tweens acting on the specified object + Useful for manipulating tweens on the fly""" + tweens = [] + for t in self.currentTweens: + if t.target is obj: + tweens.append(t) + return tweens + + def removeTweeningFrom(self, obj): + """Stop tweening an object, without completing the motion + or firing the completeFunction""" + for t in self.currentTweens: + if t.target is obj: + t.complete = True + + def finish(self): + #go to last frame for all tweens + for t in self.currentTweens: + t.update(t.duration) + self.currentTweens = [] + + def update(self, timeSinceLastFrame): + removable = [] + for t in self.currentTweens: + t.update(timeSinceLastFrame) + + if t.complete: + removable.append(t) + + for t in removable: + self.currentTweens.remove(t) + + +class Tween(object): + def __init__(self, obj, tduration, tweenType, completeFunction, updateFunction, delay, **kwargs): + """Tween object: + Can be created directly, but much more easily using Tweener.addTween( ... ) + """ + #print obj, tduration, kwargs + self.duration = tduration + self.delay = delay + self.target = obj + self.tween = tweenType + self.tweenables = kwargs + self.delta = 0 + self.completeFunction = completeFunction + self.updateFunction = updateFunction + self.complete = False + self.tProps = [] + self.tFuncs = [] + self.paused = self.delay > 0 + self.decodeArguments() + + def decodeArguments(self): + """Internal setup procedure to create tweenables and work out + how to deal with each""" + + if len(self.tweenables) == 0: + # nothing to do + print "TWEEN ERROR: No Tweenable properties or functions defined" + self.complete = True + return + + assert(len(self.tweenables) == 2) + + initial_value = self.tweenables.pop('initial_value') + + + for k, v in self.tweenables.items(): + + # check that its compatible + if not hasattr( self.target, k): + print "TWEEN ERROR: " + str(self.target) + " has no function " + k + self.complete = True + break + + prop = func = False + startVal = 0 + newVal = v + + try: + startVal = self.target.__dict__[k] + prop = k + propName = k + + except: + func = getattr( self.target, k) + funcName = k + + if func: + try: + getFunc = getattr(self.target, funcName.replace("set", "get") ) + startVal = getFunc() + print getfunc + except: + # no start value, assume its 0 + # but make sure the start and change + # dataTypes match :) + startVal = newVal * 0 + + startVal = initial_value + tweenable = Tweenable( startVal, newVal - startVal) + newFunc = [ k, func, tweenable] + + #setattr(self, funcName, newFunc[2]) + self.tFuncs.append( newFunc ) + + + if prop: + tweenable = Tweenable( startVal, newVal - startVal) + newProp = [ k, prop, tweenable] + self.tProps.append( newProp ) + + """ + for k, v in self.tweenables.items(): + + # check that its compatible + if not hasattr( self.target, k): + print "TWEEN ERROR: " + str(self.target) + " has no function " + k + self.complete = True + break + + prop = func = False + startVal = 0 + newVal = v + + try: + startVal = self.target.__dict__[k] + prop = k + propName = k + + except: + func = getattr( self.target, k) + funcName = k + + if func: + try: + getFunc = getattr(self.target, funcName.replace("set", "get") ) + startVal = getFunc() + print getfunc + except: + # no start value, assume its 0 + # but make sure the start and change + # dataTypes match :) + startVal = newVal * 0 + tweenable = Tweenable( startVal, newVal - startVal) + newFunc = [ k, func, tweenable] + + #setattr(self, funcName, newFunc[2]) + self.tFuncs.append( newFunc ) + + + if prop: + tweenable = Tweenable( startVal, newVal - startVal) + newProp = [ k, prop, tweenable] + self.tProps.append( newProp ) + """ + + + def pause( self, numSeconds=-1 ): + """Pause this tween + do tween.pause( 2 ) to pause for a specific time + or tween.pause() which pauses indefinitely.""" + self.paused = True + self.delay = numSeconds + + def resume( self ): + """Resume from pause""" + if self.paused: + self.paused=False + + def update(self, ptime): + """Update this tween with the time since the last frame + if there is an update function, it is always called + whether the tween is running or paused""" + + if self.complete: + return + + if self.paused: + if self.delay > 0: + self.delay = max( 0, self.delay - ptime ) + if self.delay == 0: + self.paused = False + self.delay = -1 + if self.updateFunction: + self.updateFunction() + return + + self.delta = min(self.delta + ptime, self.duration) + + + for propName, prop, tweenable in self.tProps: + self.target.__dict__[prop] = self.tween( self.delta, tweenable.startValue, tweenable.change, self.duration ) + for funcName, func, tweenable in self.tFuncs: + func( self.tween( self.delta, tweenable.startValue, tweenable.change, self.duration ) ) + + + if self.delta == self.duration: + self.complete = True + if self.completeFunction: + self.completeFunction() + + if self.updateFunction: + self.updateFunction() + + + + def getTweenable(self, name): + """Return the tweenable values corresponding to the name of the original + tweening function or property. + + Allows the parameters of tweens to be changed at runtime. The parameters + can even be tweened themselves! + + eg: + + # the rocket needs to escape!! - we're already moving, but must go faster! + twn = tweener.getTweensAffectingObject( myRocket )[0] + tweenable = twn.getTweenable( "thrusterPower" ) + tweener.addTween( tweenable, change=1000.0, tweenTime=0.4, tweenType=tweener.IN_QUAD ) + + """ + ret = None + for n, f, t in self.tFuncs: + if n == name: + ret = t + return ret + for n, p, t in self.tProps: + if n == name: + ret = t + return ret + return ret + + def Remove(self): + """Disables and removes this tween + without calling the complete function""" + self.complete = True + + +class Tweenable: + def __init__(self, start, change): + """Tweenable: + Holds values for anything that can be tweened + these are normally only created by Tweens""" + self.startValue = start + self.change = change + + +"""Robert Penner's easing classes ported over from actionscript by Toms Baugis (at gmail com). +There certainly is room for improvement, but wanted to keep the readability to some extent. + +================================================================================ + Easing Equations + (c) 2003 Robert Penner, all rights reserved. + This work is subject to the terms in + http://www.robertpenner.com/easing_terms_of_use.html. +================================================================================ + +TERMS OF USE - EASING EQUATIONS + +Open source under the BSD License. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the author nor the names of contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" +class Easing: + class Back: + @staticmethod + def easeIn(t, b, c, d, s = 1.70158): + t = t / d + return c * t**2 * ((s+1) * t - s) + b + + @staticmethod + def easeOut (t, b, c, d, s = 1.70158): + t = t / d - 1 + return c * (t**2 * ((s + 1) * t + s) + 1) + b + + @staticmethod + def easeInOut (t, b, c, d, s = 1.70158): + t = t / (d * 0.5) + s = s * 1.525 + + if t < 1: + return c * 0.5 * (t**2 * ((s + 1) * t - s)) + b + + t = t - 2 + return c / 2 * (t**2 * ((s + 1) * t + s) + 2) + b + + class Bounce: + @staticmethod + def easeOut (t, b, c, d): + t = t / d + if t < 1 / 2.75: + return c * (7.5625 * t**2) + b + elif t < 2 / 2.75: + t = t - 1.5 / 2.75 + return c * (7.5625 * t**2 + 0.75) + b + elif t < 2.5 / 2.75: + t = t - 2.25 / 2.75 + return c * (7.5625 * t**2 + .9375) + b + else: + t = t - 2.625 / 2.75 + return c * (7.5625 * t**2 + 0.984375) + b + + @staticmethod + def easeIn (t, b, c, d): + return c - Easing.Bounce.easeOut(d-t, 0, c, d) + b + + @staticmethod + def easeInOut (t, b, c, d): + if t < d * 0.5: + return Easing.Bounce.easeIn (t * 2, 0, c, d) * .5 + b + + return Easing.Bounce.easeOut (t * 2 -d, 0, c, d) * .5 + c*.5 + b + + + + class Circ: + @staticmethod + def easeIn (t, b, c, d): + t = t / d + return -c * (math.sqrt(1 - t**2) - 1) + b + + @staticmethod + def easeOut (t, b, c, d): + t = t / d - 1 + return c * math.sqrt(1 - t**2) + b + + @staticmethod + def easeInOut (t, b, c, d): + t = t / (d * 0.5) + if t < 1: + return -c * 0.5 * (math.sqrt(1 - t**2) - 1) + b + + t = t - 2 + return c*0.5 * (math.sqrt(1 - t**2) + 1) + b + + + class Cubic: + @staticmethod + def easeIn (t, b, c, d): + t = t / d + return c * t**3 + b + + @staticmethod + def easeOut (t, b, c, d): + t = t / d - 1 + return c * (t**3 + 1) + b + + @staticmethod + def easeInOut (t, b, c, d): + t = t / (d * 0.5) + if t < 1: + return c * 0.5 * t**3 + b + + t = t - 2 + return c * 0.5 * (t**3 + 2) + b + + + class Elastic: + @staticmethod + def easeIn (t, b, c, d, a = 0, p = 0): + if t==0: return b + + t = t / d + if t == 1: return b+c + + if not p: p = d * .3; + + if not a or a < abs(c): + a = c + s = p / 4 + else: + s = p / (2 * math.pi) * math.asin(c / a) + + t = t - 1 + return - (a * math.pow(2, 10 * t) * math.sin((t*d-s) * (2 * math.pi) / p)) + b + + + @staticmethod + def easeOut (t, b, c, d, a = 0, p = 0): + if t == 0: return b + + t = t / d + if (t == 1): return b + c + + if not p: p = d * .3; + + if not a or a < abs(c): + a = c + s = p / 4 + else: + s = p / (2 * math.pi) * math.asin(c / a) + + return a * math.pow(2,-10 * t) * math.sin((t * d - s) * (2 * math.pi) / p) + c + b + + + @staticmethod + def easeInOut (t, b, c, d, a = 0, p = 0): + if t == 0: return b + + t = t / (d * 0.5) + if t == 2: return b + c + + if not p: p = d * (.3 * 1.5) + + if not a or a < abs(c): + a = c + s = p / 4 + else: + s = p / (2 * math.pi) * math.asin(c / a) + + if (t < 1): + t = t - 1 + return -.5 * (a * math.pow(2, 10 * t) * math.sin((t * d - s) * (2 * math.pi) / p)) + b + + t = t - 1 + return a * math.pow(2, -10 * t) * math.sin((t * d - s) * (2 * math.pi) / p) * .5 + c + b + + + class Expo: + @staticmethod + def easeIn(t, b, c, d): + if t == 0: + return b + else: + return c * math.pow(2, 10 * (t / d - 1)) + b - c * 0.001 + + @staticmethod + def easeOut(t, b, c, d): + if t == d: + return b + c + else: + return c * (-math.pow(2, -10 * t / d) + 1) + b + + @staticmethod + def easeInOut(t, b, c, d): + if t==0: + return b + elif t==d: + return b+c + + t = t / (d * 0.5) + + if t < 1: + return c * 0.5 * math.pow(2, 10 * (t - 1)) + b + + return c * 0.5 * (-math.pow(2, -10 * (t - 1)) + 2) + b + + + class Linear: + @staticmethod + def easeNone(t, b, c, d): + return c * t / d + b + + @staticmethod + def easeIn(t, b, c, d): + return c * t / d + b + + @staticmethod + def easeOut(t, b, c, d): + return c * t / d + b + + @staticmethod + def easeInOut(t, b, c, d): + return c * t / d + b + + + class Quad: + @staticmethod + def easeIn (t, b, c, d): + t = t / d + return c * t**2 + b + + @staticmethod + def easeOut (t, b, c, d): + t = t / d + return -c * t * (t-2) + b + + @staticmethod + def easeInOut (t, b, c, d): + t = t / (d * 0.5) + if t < 1: + return c * 0.5 * t**2 + b + + t = t - 1 + return -c * 0.5 * (t * (t - 2) - 1) + b + + + class Quart: + @staticmethod + def easeIn (t, b, c, d): + t = t / d + return c * t**4 + b + + @staticmethod + def easeOut (t, b, c, d): + t = t / d - 1 + return -c * (t**4 - 1) + b + + @staticmethod + def easeInOut (t, b, c, d): + t = t / (d * 0.5) + if t < 1: + return c * 0.5 * t**4 + b + + t = t - 2 + return -c * 0.5 * (t**4 - 2) + b + + + class Quint: + @staticmethod + def easeIn (t, b, c, d): + t = t / d + return c * t**5 + b + + @staticmethod + def easeOut (t, b, c, d): + t = t / d - 1 + return c * (t**5 + 1) + b + + @staticmethod + def easeInOut (t, b, c, d): + t = t / (d * 0.5) + if t < 1: + return c * 0.5 * t**5 + b + + t = t - 2 + return c * 0.5 * (t**5 + 2) + b + + class Sine: + @staticmethod + def easeIn (t, b, c, d): + return -c * math.cos(t / d * (math.pi / 2)) + c + b + + @staticmethod + def easeOut (t, b, c, d): + return c * math.sin(t / d * (math.pi / 2)) + b + + @staticmethod + def easeInOut (t, b, c, d): + return -c * 0.5 * (math.cos(math.pi * t / d) - 1) + b + + + class Strong: + @staticmethod + def easeIn(t, b, c, d): + return c * (t/d)**5 + b + + @staticmethod + def easeOut(t, b, c, d): + return c * ((t / d - 1)**5 + 1) + b + + @staticmethod + def easeInOut(t, b, c, d): + t = t / (d * 0.5) + + if t < 1: + return c * 0.5 * t**5 + b + + t = t - 2 + return c * 0.5 * (t**5 + 2) + b + + + +class TweenTestObject: + def __init__(self): + self.pos = 20 + self.rot = 50 + + def update(self): + print self.pos, self.rot + + def setRotation(self, rot): + self.rot = rot + + def getRotation(self): + return self.rot + + def complete(self): + print "I'm done tweening now mommy!" + + +if __name__=="__main__": + import time + T = Tweener() + tst = TweenTestObject() + mt = T.addTween( tst, setRotation=500.0, tweenTime=2.5, tweenType=T.OUT_QUAD, + pos=-200, tweenDelay=0.4, onCompleteFunction=tst.complete, + onUpdateFunction=tst.update ) + s = time.clock() + changed = False + while T.hasTweens(): + tm = time.clock() + d = tm - s + s = tm + T.update( d ) + if mt.delta > 1.0 and not changed: + + tweenable = mt.getTweenable( "setRotation" ) + + T.addTween( tweenable, change=-1000, tweenTime=0.7 ) + T.addTween( mt, duration=-0.2, tweenTime=0.2 ) + changed = True + #print mt.duration, + print tst.getRotation(), tst.pos + time.sleep(0.06) + print tst.getRotation(), tst.pos diff --git a/pilas/red.py b/pilas/red.py new file mode 100644 index 0000000..7d28fdb --- /dev/null +++ b/pilas/red.py @@ -0,0 +1,41 @@ +# -*- encoding: utf-8 -*- +# pilas engine - a video game framework. +# +# copyright 2010 - hugo ruscitti +# license: lgplv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# website - http://www.pilas-engine.com.ar + +import SocketServer + + + +def iniciar_servidor(): + + + class EchoRequestHandler(SocketServer.BaseRequestHandler ): + def setup(self): + print self.client_address, 'connected!' + self.request.send('hi ' + str(self.client_address) + '\n') + + def handle(self): + data = 'dummy' + while data: + data = self.request.recv(1024) + print "ha llegado el mensaje:", data + self.request.send(data) + + if data.strip() == 'bye': + return + + def finish(self): + print self.client_address, 'disconnected!' + self.request.send('bye ' + str(self.client_address) + '\n') + + + #server host is a tuple ('host', port) + puerto = 50008 + print "iniciando el modo servidor en el puerto %d" %(puerto) + + servidor = SocketServer.ThreadingTCPServer(('', puerto), EchoRequestHandler) + servidor.serve_forever() diff --git a/pilas/simbolos.py b/pilas/simbolos.py new file mode 100644 index 0000000..4c5afed --- /dev/null +++ b/pilas/simbolos.py @@ -0,0 +1,19 @@ +IZQUIERDA = 1 +DERECHA = 2 +ARRIBA = 3 +ABAJO = 4 +BOTON = 5 +SELECCION = 6 + +F1='F1' +F2='F2' +F3='F3' +F4='F4' +F5='F5' +F6='F6' +F7='F7' +F8='F8' +F9='F9' +F10='F10' +F11='F11' +F12='F12' diff --git a/pilas/sonidos.py b/pilas/sonidos.py new file mode 100644 index 0000000..dadc1e9 --- /dev/null +++ b/pilas/sonidos.py @@ -0,0 +1,36 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +import pilas +import os + + +def cargar(ruta): + """Carga un sonido para reproducir, donde el argumento ``ruta`` indica cual es el archivo. + + Por ejemplo:: + + import pilas + + risa = pilas.sonidos.cargar("risa.ogg") + + En caso de éxito retorna el objeto Sound, que se puede + reproducir usando el método ``reproducir()``, por ejemplo:: + + risa.reproducir() + + El directorio de búsqueda del sonido sigue el siguiente orden: + + * primero busca en el directorio actual. + * luego en 'data'. + * por último en el directorio estándar de la biblioteca. + + En caso de error genera una excepción de tipo IOError. + """ + ruta = pilas.utils.obtener_ruta_al_recurso(ruta) + return pilas.mundo.motor.cargar_sonido(ruta) diff --git a/pilas/tareas.py b/pilas/tareas.py new file mode 100644 index 0000000..c94c2e8 --- /dev/null +++ b/pilas/tareas.py @@ -0,0 +1,111 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +class Tarea(object): + + def __init__(self, time_out, dt, funcion, parametros, una_vez): + """ + + Parametros: + + - time_out: el tiempo absoluto para ejecutar la tarea. + - dt: la frecuencia de ejecución. + - funcion: la funcion a invocar. + - parametros: una lista de argumentos para la funcion anterior. + - una_vez: indica si la funcion se tiene que ejecutar una sola vez. + """ + + self.time_out = time_out + self.dt = dt + self.funcion = funcion + self.parametros = parametros + self.una_vez = una_vez + self.activa = True + + def ejecutar(self): + return self.funcion(*self.parametros) + + def eliminar(self): + self.activa = False + +class TareaCondicional(Tarea): + + def ejecutar(self): + retorno = Tarea.ejecutar(self) + + if not retorno: + self.una_vez = True + +class Tareas(object): + """Contenedor de tareas a ejecutar por tiempo. + + El Tareas es un planificador de tareas, permite que + podamos ejecutar funciones y métodos luego de transcurrido + el tiempo que queramos. + + Por ejemplo, si se quiere que el planificardor ejecute + una función dentro de dos segundos podemos escribir: + + pilas.mundo.agregar_tarea(2, hola) + + o bien, especificando argumentos para esa función: + + pilas.mundo.agregar_tarea(4, hola, 'persona') + + La función que se especifique como segundo argumento + tiene que retornar True o False. Si retorna True será + colocada nuevamente en la cola de tareas una vez que se + ejecute (esto es útil para crear bucles). + """ + + def __init__(self): + self.tareas_planificadas = [] + self.contador_de_tiempo = 0 + + def actualizar(self, dt): + "Actualiza los contadores de tiempo y ejecuta las tareas pendientes." + self.contador_de_tiempo += dt + to_remove = [] + + for tarea in self.tareas_planificadas: + if self.contador_de_tiempo > tarea.time_out: + if tarea.activa: + tarea.ejecutar() + + if tarea.una_vez: + self.tareas_planificadas.remove(tarea) + else: + w = self.contador_de_tiempo - tarea.time_out + parte_entera = int((w)/float(tarea.dt)) + resto = w - (parte_entera * tarea.dt) + + for x in range(parte_entera): + tarea.ejecutar() + + tarea.time_out += tarea.dt + (parte_entera * tarea.dt) - resto + else: + self.tareas_planificadas.remove(tarea) + + def _agregar(self, tarea): + "Agrega una nueva tarea para ejecutarse luego." + self.tareas_planificadas.append(tarea) + + def una_vez(self, time_out, function, params=[]): + tarea = Tarea(self.contador_de_tiempo + time_out, time_out, function, params, True) + self._agregar(tarea) + return tarea + + def siempre(self, time_out, function, params=[]): + tarea = Tarea(self.contador_de_tiempo + time_out, time_out, function, params, False) + self._agregar(tarea) + return tarea + + def condicional(self, time_out, function, params=[]): + tarea = TareaCondicional(self.contador_de_tiempo + time_out, time_out, function, params, False) + self._agregar(tarea) + return tarea diff --git a/pilas/test.html b/pilas/test.html new file mode 100644 index 0000000..557db03 --- /dev/null +++ b/pilas/test.html @@ -0,0 +1 @@ +Hello World diff --git a/pilas/test/test.py b/pilas/test/test.py new file mode 100644 index 0000000..27a0fc8 --- /dev/null +++ b/pilas/test/test.py @@ -0,0 +1,4 @@ +import os + +if __name__ == '__main__': + os.system("py.test -v --tb=short") diff --git a/pilas/test/test_actores.py b/pilas/test/test_actores.py new file mode 100644 index 0000000..7b8221b --- /dev/null +++ b/pilas/test/test_actores.py @@ -0,0 +1,73 @@ +import pilas + +def test_posicion_de_los_actores(): + pilas.iniciar() + mono = pilas.actores.Mono() + + # el actor comienza en el centro de la ventana + assert mono.x == 0 + assert mono.y == 0 + + # un cambio de posicion sencillo + mono.x = 100 + mono.y = 100 + assert mono.x == 100 + assert mono.y == 100 + + # rotacion + assert mono.rotacion == 0 + + mono.rotacion = 180 + assert mono.rotacion == 180 + + # Verificnado que las rotaciones siempre estan entre 0 y 360 + mono.rotacion = 361 + assert mono.rotacion == 1 + + mono.rotacion = -10 + assert mono.rotacion == 350 + + # Analizando el actor existira en la escena + assert mono in pilas.actores.todos + + # Escalas + assert mono.escala == 1 + + mono.escala = 0 + assert mono.escala == 0 + + mono.escala = 0.5 + assert mono.escala == 0.5 + + mono.escala = 5 + assert mono.escala == 5 + + # verificando que el mono se elimina de la escena. + mono.eliminar() + assert not (mono in pilas.actores.todos) + + +def test_correlacion_de_posiciones(): + mono = pilas.actores.Mono() + + assert mono.x == 0 + assert mono.y == 0 + + mono.izquierda = mono.izquierda - 100 + assert mono.x == -100 + + mono.derecha = mono.derecha + 100 + assert mono.x == 0 + + mono.arriba = mono.arriba + 100 + assert mono.y == 100 + + mono.abajo = mono.abajo - 100 + assert mono.y == 0 + + mono.eliminar() + +def test_colisiones_contra_un_punto(): + mono = pilas.actores.Mono() + assert mono.colisiona_con_un_punto(0, 0) + assert not mono.colisiona_con_un_punto(200, 200) diff --git a/pilas/test/test_anterior.py b/pilas/test/test_anterior.py new file mode 100644 index 0000000..7ac7121 --- /dev/null +++ b/pilas/test/test_anterior.py @@ -0,0 +1,117 @@ +# -*- encoding: utf-8 -*- +import pilas + +def test_existe_mundo(): + pilas.iniciar() + assert pilas.mundo + +def test_cargar_imagenes(): + pilas.iniciar() + original_image = pilas.imagenes.cargar('mono.png') + + actor = pilas.actores.Actor(original_image) + actors_image = actor.imagen + + assert original_image == actors_image + +def test_planificador(): + pilas.iniciar() + pilas.mundo.agregar_tarea_una_vez(2, None) + pilas.mundo.agregar_tarea_una_vez(2, None, (1, 2, 3)) + +def test_interpolacion(): + pilas.iniciar() + a = pilas.interpolar([0, 100]) + assert a.values == [0, 100] + + # Invierte la interpolacion. + a = -a + assert a.values == [100, 0] + +def test_actor_texto(): + pilas.iniciar() + texto = pilas.actores.Texto("Hola") + assert texto.texto == "Hola" + + # verificando que el tamaño inicial es de 30 y el color negro + assert texto.magnitud == 30 + +def test_habilidades(): + texto = pilas.actores.Texto("Hola") + + # Vincula la clase Text con un componente. + component = pilas.habilidades.AumentarConRueda + texto.aprender(component) + + # Se asegura que el componente pasa a ser de la superclase. + assert component == texto.habilidades[0].__class__ + +def test_existen_los_atajos(): + assert pilas.atajos + +def test_Ejes(): + ejes = pilas.actores.Ejes() + assert ejes + + +def test_Grilla(): + grilla = pilas.imagenes.cargar_grilla("fondos/volley.png", 10, 10) + assert grilla + grilla.avanzar() + +def test_Fondo(): + un_fondo = pilas.fondos.Tarde() + assert un_fondo + +def test_Control(): + control = pilas.mundo.control + + assert control.izquierda + assert control.derecha + assert control.arriba + assert control.abajo + assert control.boton + + +def test_Distancias(): + assert 0 == pilas.utils.distancia(0, 0) + assert 10 == pilas.utils.distancia(0, 10) + assert 10 == pilas.utils.distancia(0, -10) + assert 10 == pilas.utils.distancia(-10, 0) + + assert 0 == pilas.utils.distancia_entre_dos_puntos((0, 0), (0, 0)) + assert 10 == pilas.utils.distancia_entre_dos_puntos((0, 0), (10, 0)) + assert 10 == pilas.utils.distancia_entre_dos_puntos((0, 0), (0, 10)) + assert 10 == pilas.utils.distancia_entre_dos_puntos((10, 10), (0, 10)) + + +def test__posiciones_del_texto(): + m = pilas.actores.Texto("Hola") + assert m.x == 0 + + ancho = m.obtener_ancho() + algo = m.obtener_alto() + + # Verifica que la izquierda del actor esté asociada a la + # posición 'x'. + m.izquierda = m.izquierda - 50 + assert m.x == -50 + + m.izquierda = m.izquierda - 50 + assert m.x == -100 + + # Analiza si la parte derecha del actor esta vinculada a 'x' + m.derecha = m.derecha + 50 + assert m.x == -50 + + # Verifica si la posicion superior e inferior alteran a 'y' + assert m.y == 0 + m.arriba = m.arriba - 100 + assert m.y == -100 + + m.abajo = m.abajo + 100 + assert m.y == 0 + +if __name__ == '__main__': + pilas.iniciar() + unittest.main() diff --git a/pilas/test/test_interface.py b/pilas/test/test_interface.py new file mode 100644 index 0000000..f76dd81 --- /dev/null +++ b/pilas/test/test_interface.py @@ -0,0 +1,30 @@ +import pilas + +def test_todos_los_objetos_de_interfaz_se_pueden_crear(): + pilas.iniciar() + + deslizador = pilas.interfaz.Deslizador() + assert deslizador + assert deslizador.progreso == 0 + + boton = pilas.interfaz.Boton() + assert boton + + ingreso = pilas.interfaz.IngresoDeTexto() + assert ingreso + + try: + pilas.interfaz.ListaSeleccion() + except TypeError: + assert True # Se espera esta excepcion, porque un argumento es obligatorio + + lista = pilas.interfaz.ListaSeleccion([('uno')]) + assert lista + + try: + pilas.interfaz.Selector() + except TypeError: + assert True # el argumento texto es obligatorio. + + selector = pilas.interfaz.Selector("hola") + assert selector diff --git a/pilas/test/test_posiciones.py b/pilas/test/test_posiciones.py new file mode 100644 index 0000000..69f9cae --- /dev/null +++ b/pilas/test/test_posiciones.py @@ -0,0 +1,246 @@ +import pilas + +def test_posiciones_de_los_actores(): + pilas.iniciar() + + caja = pilas.actores.Actor("caja.png") + + # +------------+ + # | | + # | | + # | x | + # | | + # | | + # +------------+ + + assert caja.alto == 48 + assert caja.ancho == 48 + + assert caja.arriba == 24 + assert caja.abajo == -24 + assert caja.izquierda == -24 + assert caja.derecha == 24 + + +def test_escala_reducida(): + pilas.iniciar() + caja = pilas.actores.Actor("caja.png") + caja.escala = 0.5 + + # +------------+ # La caja resultado + # | | # es la interior. + # | +----+ | + # | | | | + # | +----+ | + # | | + # +------------+ + + assert caja.alto == 24 + assert caja.ancho == 24 + + assert caja.x == 0 + assert caja.y == 0 + + assert caja.arriba == 12 + assert caja.abajo == -12 + assert caja.izquierda == -12 + assert caja.derecha == 12 + +def test_escala_ampliada(): + pilas.iniciar() + caja = pilas.actores.Actor("caja.png") + caja.escala = 2 + + assert caja.alto == 48*2 + assert caja.ancho == 48*2 + + assert caja.x == 0 + assert caja.y == 0 + + assert caja.arriba == 48 + assert caja.abajo == -48 + assert caja.izquierda == -48 + assert caja.derecha == 48 + + +def test_cambio_horizontal_de_centro(): + pilas.iniciar() + caja = pilas.actores.Actor("caja.png") + caja.escala = 1 + caja.centro = ("izquierda", "centro") + # +------------+ + # | | + # | | + # |x | + # | | + # | | + # +------------+ + + assert caja.alto == 48 + assert caja.ancho == 48 + + assert caja.arriba == 24 + assert caja.abajo == -24 + assert caja.izquierda == 0 + assert caja.derecha == 48 + + caja.centro = ("derecha", "centro") + # +------------+ + # | | + # | | + # | x| + # | | + # | | + # +------------+ + + assert caja.arriba == 24 + assert caja.abajo == -24 + assert caja.izquierda == -48 + assert caja.derecha == 0 + + +def test_cambiocentrovertical(): + caja = pilas.actores.Actor("caja.png") + caja.escala = 1 + caja.centro = ("centro", "arriba") + # +------------+ + # | x | + # | | + # | | + # | | + # | | + # +------------+ + + assert caja.alto == 48 + assert caja.ancho == 48 + + assert caja.abajo == -48 + assert caja.arriba == 0 + + assert caja.izquierda == -24 + assert caja.derecha == 24 + + caja.centro = ("centro", "abajo") + # +------------+ + # | | + # | | + # | | + # | | + # | x | + # +------------+ + + assert caja.arriba == 48 + assert caja.abajo == 0 + assert caja.izquierda == -24 + assert caja.derecha == 24 + +def test_cambiocentroverticalyhorizontalnocentrado(): + caja = pilas.actores.Actor("caja.png") + caja.escala = 1 + + caja.centro = (10, 10) + + # +------------+ + # | | + # | x | + # | | + # | | + # | | + # +------------+ + + assert caja.alto == 48 + assert caja.ancho == 48 + + assert caja.abajo == -38 + assert caja.arriba == 10 + + assert caja.izquierda == -10 + assert caja.derecha == 38 + +def test_cambiocentroverticalyhorizontalnocentradoconreducciondeescala(): + caja = pilas.actores.Actor("caja.png") + caja.escala = 0.5 + + caja.centro = (10, 10) + + # +------------+ + # | | + # | x | + # | | + # | | + # | | + # +------------+ + + assert caja.alto == 24 + assert caja.ancho == 24 + + assert caja.abajo == -38/2 + assert caja.arriba == 5 + + assert caja.izquierda == -5 + assert caja.derecha == 38/2 + +def test_cambiodeposicionhorizontalconescala(): + caja = pilas.actores.Actor("caja.png") + caja.escala = 1 + + assert caja.izquierda == -24 + assert caja.derecha == 24 + + # Prueba dos cambios que no tendrian que afectar + caja.izquierda = -24 + assert caja.izquierda == -24 + assert caja.derecha == 24 + + caja.derecha = 24 + assert caja.izquierda == -24 + assert caja.derecha == 24 + + caja.escala = 0.5 + + assert caja.izquierda == -12 + assert caja.derecha == 12 + + # Prueba dos cambios que no tendrian que afectar + caja.izquierda = -20 + assert caja.izquierda == -20 + assert caja.derecha == -20 + 24 + + caja.derecha = 0 + assert caja.izquierda == -24 + assert caja.derecha == 0 + +def test_cambiodeposicionverticalconescala(): + caja = pilas.actores.Actor("caja.png") + caja.centro = ("centro", "centro") + caja.escala = 1 + + assert caja.area == 48 + assert caja.arriba == 24 + assert caja.abajo == -24 + + caja.arriba = 0 + assert caja.arriba == 0 + assert caja.abajo == -48 + + caja.abajo = 0 + assert caja.arriba == 48 + assert caja.abajo == 0 + + + caja.escala = 0.5 + assert caja.area, (24 == 24) + caja.x, caja.y = (0, 0) + + assert caja.arriba == 12 + assert caja.abajo == -12 + + # Prueba dos cambios que no tendrian que afectar + caja.izquierda = -20 + assert caja.izquierda == -20 + assert caja.derecha == -20 + 24 + + caja.derecha = 0 + assert caja.izquierda == -24 + assert caja.derecha == 0 + diff --git a/pilas/test/test_ver_codigo.py b/pilas/test/test_ver_codigo.py new file mode 100644 index 0000000..9cefe10 --- /dev/null +++ b/pilas/test/test_ver_codigo.py @@ -0,0 +1,7 @@ +import pilas + +def test_ver_codigo(): + pilas.iniciar() + assert pilas.ver(pilas, False, True) + assert pilas.ver(pilas.habilidades, False, True) + assert pilas.ver(pilas.habilidades.Arrastrable, False, True) diff --git a/pilas/utils.py b/pilas/utils.py new file mode 100644 index 0000000..b2fb93a --- /dev/null +++ b/pilas/utils.py @@ -0,0 +1,246 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar +import os +import shutil +import interpolaciones +import sys +import subprocess +import math + +import pilas +import xmlreader + + +PATH = os.path.dirname(os.path.abspath(__file__)) + + +def cargar_autocompletado(): + "Carga los modulos de python para autocompletar desde la consola interactiva." + try: + import rlcompleter + import readline + + readline.parse_and_bind("tab: complete") + except ImportError: + print "No se puede cargar el autocompletado, instale readline..." + +def hacer_flotante_la_ventana(): + "Hace flotante la ventana para i3 (el manejador de ventanas que utiliza hugo...)" + try: + subprocess.call(['i3-msg', 't'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except OSError: + pass + +def es_interpolacion(an_object): + "Indica si un objeto se comporta como una colisión." + + return isinstance(an_object, interpolaciones.Interpolacion) + + +def obtener_ruta_al_recurso(ruta): + """Busca la ruta a un archivo de recursos. + + Los archivos de recursos (como las imagenes) se buscan en varios + directorios (ver docstring de image.load), así que esta + función intentará dar con el archivo en cuestión. + """ + + dirs = ['./', os.path.dirname(sys.argv[0]), 'data', PATH, PATH + '/data'] + + + for x in dirs: + full_path = os.path.join(x, ruta) + #DEBUG: print "buscando en: '%s'" %(full_path) + + if os.path.exists(full_path): + return full_path + + # Si no ha encontrado el archivo lo reporta. + raise IOError("El archivo '%s' no existe." %(ruta)) + + +def esta_en_sesion_interactiva(): + "Indica si pilas se ha ejecutado desde una consola interactiva de python." + import sys + try: + cursor = sys.ps1 + return True + except AttributeError: + try: + in_ipython = sys.ipcompleter + return True + except AttributeError: + if sys.stdin.__class__.__module__.startswith("idle"): + return True + + return False + +def distancia(a, b): + "Retorna la distancia entre dos numeros." + return abs(b - a) + +def distancia_entre_dos_puntos((x1, y1), (x2, y2)): + "Retorna la distancia entre dos puntos en dos dimensiones." + return math.sqrt(distancia(x1, x2) ** 2 + distancia(y1, y2) ** 2) + +def distancia_entre_dos_actores(a, b): + return distancia_entre_dos_puntos((a.x, a.y), (b.x, b.y)) + +def colisionan(a, b): + "Retorna True si dos actores estan en contacto." + return distancia_entre_dos_actores(a, b) < a.radio_de_colision + b.radio_de_colision + +def crear_juego(): + nombre = raw_input("Indica el nombre del juego: ") + shutil.copytree(PATH + "/data/juegobase", nombre) + + print "Se ha creado el directorio '%s'" %(nombre) + print "Ingresa en el directorio y econtrarás los archivos iniciales del juego." + + +def interpolable(f): + "Decorador que se aplica a un metodo para que permita animaciones de interpolaciones." + + def inner(*args, **kwargs): + value = args[1] + + # Si le indican dos argumentos, el primer sera + # el valor de la interpolacion y el segundo la + # velocidad. + if isinstance(value, tuple) and len(value) == 2: + duracion = value[1] + value = value[0] + else: + duracion = 1 + + if isinstance(value, list): + value = interpolar(value, duracion=duracion) + elif isinstance(value, xrange): + value = interpolar(list(value), duracion=duracion) + + if es_interpolacion(value): + value.apply(args[0], function=f.__name__) + else: + f(args[0], value, **kwargs) + + return inner + +def hacer_coordenada_mundo(x, y): + dx, dy = pilas.mundo.motor.centro_fisico() + return (x + dx, dy - y) + +def hacer_coordenada_pantalla_absoluta(x, y): + dx, dy = pilas.mundo.motor.centro_fisico() + return (x + dx, dy - y) + +def listar_actores_en_consola(): + todos = pilas.actores.todos + + print "Hay %d actores en la escena:" %(len(todos)) + print "" + + for s in todos: + print "\t", s + + print "" + +def obtener_angulo_entre(punto_a, punto_b): + (x, y) = punto_a + (x1, y1) = punto_b + return math.degrees(math.atan2(y1 - y, x1 -x)) + +def convertir_de_posicion_relativa_a_fisica(x, y): + dx, dy = pilas.mundo.motor.centro_fisico() + return (x + dx, dy - y) + +def convertir_de_posicion_fisica_relativa(x, y): + dx, dy = pilas.mundo.motor.centro_fisico() + return (x - dx, dy - y) + +def interpolar(valor_o_valores, duracion=1, demora=0, tipo='lineal'): + """Retorna un objeto que representa cambios de atributos progresivos. + + El resultado de esta función se puede aplicar a varios atributos + de los actores, por ejemplo:: + + bomba = pilas.actores.Bomba() + bomba.escala = pilas.interpolar(3) + + Esta función también admite otros parámetros cómo: + + - duracion: es la cantidad de segundos que demorará toda la interpolación. + - demora: cuantos segundos se deben esperar antes de iniciar. + - tipo: es el algoritmo de la interpolación, puede ser 'lineal'. + """ + + + import interpolaciones + + algoritmos = { + 'lineal': interpolaciones.Lineal, + } + + if algoritmos.has_key('lineal'): + clase = algoritmos[tipo] + else: + raise ValueError("El tipo de interpolacion %s es invalido" %(tipo)) + + # Permite que los valores de interpolacion sean un numero o una lista. + if not isinstance(valor_o_valores, list): + valor_o_valores = [valor_o_valores] + + return clase(valor_o_valores, duracion, demora) + +def obtener_area(): + "Retorna el area que ocupa la ventana" + return pilas.mundo.motor.obtener_area() + +def obtener_bordes(): + ancho, alto = pilas.mundo.motor.obtener_area() + return -ancho/2, ancho/2, alto/2, -alto/2 + +def obtener_area_de_texto(texto): + "Informa el ancho y alto que necesitara un texto para imprimirse." + return pilas.mundo.motor.obtener_area_de_texto(texto) + +def realizar_pruebas(): + print "Realizando pruebas de dependencias:" + print "" + + print "Box 2D:", + + try: + import Box2D as box2d + print "OK, versión", box2d.__version__ + except ImportError: + print "Error -> no se encuentra pybox2d." + + print "pygame:", + + try: + import pygame + print "OK, versión", pygame.__version__ + except ImportError: + print "Error -> no se encuentra pygame." + + print "pyqt:", + + try: + from PyQt4 import Qt + print "OK, versión", Qt.PYQT_VERSION_STR + except ImportError: + print "Error -> no se encuentra pyqt." + + print "pyqt con aceleracion:", + + try: + from PyQt4 import QtOpenGL + from PyQt4.QtOpenGL import QGLWidget + print "OK" + except ImportError: + print "Error -> no se encuentra pyqt4gl." diff --git a/pilas/ventana.py b/pilas/ventana.py new file mode 100644 index 0000000..b53e7d2 --- /dev/null +++ b/pilas/ventana.py @@ -0,0 +1,16 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + +import pilas + +modo_depuracion = False +eje_coordenadas = None + +def iniciar(ancho, alto, titulo): + ventana = pilas.motor.crear_ventana(ancho, alto, titulo) + return ventana diff --git a/pilas/video/__init__.py b/pilas/video/__init__.py new file mode 100644 index 0000000..c08c026 --- /dev/null +++ b/pilas/video/__init__.py @@ -0,0 +1,18 @@ +# -*- encoding: utf-8 -*- +# Pilas engine - A video game framework. +# +# Copyright 2010 - Hugo Ruscitti +# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html) +# +# Website - http://www.pilas-engine.com.ar + + +''' +import video +import pilas + +todos = [] + +from video import DeCamara +from video import DePelicula +''' diff --git a/pilas/video/video.py b/pilas/video/video.py new file mode 100644 index 0000000..cdd739d --- /dev/null +++ b/pilas/video/video.py @@ -0,0 +1,95 @@ +# -*- encoding: utf-8 -*- +''' + +import pilas +try: + import opencv + from opencv import highgui +except ImportError: + opencv = None + +import os + +try: + from PySFML import sf +except ImportError: + pass + +class MissingOpencv(Exception): + def __init__(self): + self.value = "Open CV no esta instalado, obtengalo en http://opencv.willowgarage.com" + + def __str__(self): + return repr(self.value) + +def error(biblioteca, web): + print "Error, no ecuentra la biblioteca '%s' (de %s)" %(biblioteca, web) + +def no_opencv(): + from pilas.utils import esta_en_sesion_interactiva + if esta_en_sesion_interactiva(): + error('opencv', 'http://opencv.willowgarage.com') + else: + raise MissingOpencv() + +class DeCamara(pilas.actores.Actor): + """ + Nos permite poner en pantalla el video proveniente de la camara web. + + """ + def __init__(self, ancho=640, alto=480): + if opencv is None: + no_opencv() + return + import webcam + self.camara = webcam.CamaraWeb + self.ultimo_numero_de_cuadro = 0 + pilas.actores.Actor.__init__(self, 'fondos/pasto.png') + pilas.mundo.agregar_tarea_siempre(0.15,self.actualizar_video) + + def actualizar_video(self): + cuadro, numero_de_cuadro = self.camara.obtener_imagen(self.ultimo_numero_de_cuadro) + self.ultimo_numero_de_cuadro = numero_de_cuadro + self.imagen.LoadFromPixels(640, 480, cuadro) + return True + +class VideoDeArchivo(object): + def __init__(self, ruta): + if opencv is None: + no_opencv() + return + if not os.path.isfile(ruta): + raise IOError('El archiyo no existe') + self._camara = highgui.cvCreateFileCapture(ruta) + self.fps = highgui.cvGetCaptureProperty(self._camara, highgui.CV_CAP_PROP_FPS) + self.altura = highgui.cvGetCaptureProperty(self._camara, highgui.CV_CAP_PROP_FRAME_HEIGHT) + self.ancho =highgui.cvGetCaptureProperty(self._camara, highgui.CV_CAP_PROP_FRAME_WIDTH) + super(VideoDeArchivo, self).__init__() + + def obtener_imagen(self): + imagen_ipl = highgui.cvQueryFrame(self._camara) + imagen_ipl = opencv.cvGetMat(imagen_ipl) + return opencv.adaptors.Ipl2PIL(imagen_ipl).convert('RGBA').tostring() + + +class DePelicula(pilas.actores.Actor): + """ + Nos permite poner en pantalla un video desde un archivo. + Toma como parametro la ruta del video. + """ + def __init__(self, path, ancho=640, alto=480): + self._camara = VideoDeArchivo(path) + pilas.actores.Actor.__init__(self) + self._altura_cuadro = self._camara.altura + self._ancho_cuadro = self._camara.ancho + subrect = self._actor.GetSubRect() + subrect.Right = self._ancho_cuadro + subrect.Bottom = self._altura_cuadro + self._actor.SetSubRect(subrect) + self.centro = ('centro', 'centro') + pilas.mundo.agregar_tarea_siempre(1/self._camara.fps,self.actualizar_video) + + def actualizar_video(self): + self.imagen.LoadFromPixels(self._ancho_cuadro, self._altura_cuadro, self._camara.obtener_imagen()) + return True +''' diff --git a/pilas/video/webcam.py b/pilas/video/webcam.py new file mode 100644 index 0000000..b463aee --- /dev/null +++ b/pilas/video/webcam.py @@ -0,0 +1,37 @@ +''' +import pilas +try: + import Image + import opencv + from opencv import highgui + + GLOBALCAM=highgui.cvCreateCameraCapture(0) + + for algo in range(30): + ULTIMO_CUADRO_BASURA = highgui.cvQueryFrame(GLOBALCAM) + + ULTIMO_CUADRO_BASURA = opencv.adaptors.Ipl2PIL(opencv.cvGetMat(ULTIMO_CUADRO_BASURA)).convert('RGBA') +except ImportError: + print "Falta la biblioteca opencv o PIL" + pass + + +class __camara_buffer(object): + def __init__(self): + self._ultimo_numero_de_cuadro = 0 + self._camera = GLOBALCAM + self._ultimo_cuadro = ULTIMO_CUADRO_BASURA.tostring() + + def _obtener_imagen_de_camara(self): + imagen_ipl = highgui.cvQueryFrame(self._camera) + imagen_ipl = opencv.cvGetMat(imagen_ipl) + self._ultimo_cuadro = opencv.adaptors.Ipl2PIL(imagen_ipl).convert('RGBA').tostring() + + def obtener_imagen(self, numero_de_cuadro=0): + if numero_de_cuadro == self._ultimo_numero_de_cuadro: + self._obtener_imagen_de_camara() + self._ultimo_numero_de_cuadro += 1 + return self._ultimo_cuadro, self._ultimo_numero_de_cuadro + +CamaraWeb = __camara_buffer() +''' diff --git a/pilas/window_base.py b/pilas/window_base.py new file mode 100644 index 0000000..7f99bc3 --- /dev/null +++ b/pilas/window_base.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'data/window.ui' +# +# Created: Sun Sep 25 16:42:42 2011 +# by: PyQt4 UI code generator 4.8.3 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + _fromUtf8 = lambda s: s + +class Ui_Window(object): + def setupUi(self, Window): + Window.setObjectName(_fromUtf8("Window")) + Window.resize(503, 477) + self.verticalLayout = QtGui.QVBoxLayout(Window) + self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) + self.graphicsView = QtGui.QGraphicsView(Window) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.NoBrush) + self.graphicsView.setBackgroundBrush(brush) + self.graphicsView.setObjectName(_fromUtf8("graphicsView")) + self.verticalLayout.addWidget(self.graphicsView) + self.plainTextEdit = QtGui.QPlainTextEdit(Window) + self.plainTextEdit.setObjectName(_fromUtf8("plainTextEdit")) + self.verticalLayout.addWidget(self.plainTextEdit) + + self.retranslateUi(Window) + QtCore.QMetaObject.connectSlotsByName(Window) + + def retranslateUi(self, Window): + Window.setWindowTitle(QtGui.QApplication.translate("Window", "Form", None, QtGui.QApplication.UnicodeUTF8)) + self.plainTextEdit.setPlainText(QtGui.QApplication.translate("Window", "import blabla\n" +"", None, QtGui.QApplication.UnicodeUTF8)) + diff --git a/pilas/xmlreader.py b/pilas/xmlreader.py new file mode 100644 index 0000000..02fa8d3 --- /dev/null +++ b/pilas/xmlreader.py @@ -0,0 +1,33 @@ +from xml.dom import minidom + +class XmlNode: + """An XML node represents a single field in an XML document.""" + + def __init__(self, domElement): + """Construct an XML node from a DOM element.""" + self.elem = domElement + + def getData(self): + """Extract data from a DOM node.""" + for child in self.elem.childNodes: + if child.nodeType == child.TEXT_NODE: + return str(child.data) + return None + + def getAttributeValue(self, name): + """Returns the value of the attribute having the specified name.""" + return str(self.elem.attributes[name].value) + + def getChild(self, tag): + """Returns the first child node having the specified tag.""" + return XmlNode(self.elem.getElementsByTagName(tag)[0]) + + def getChildren(self, tag): + """Returns a list of child nodes having the specified tag.""" + return [XmlNode(x) for x in self.elem.getElementsByTagName(tag)] + + +def makeRootNode(xmlFileName): + """Creates the root node from an XML file.""" + return XmlNode(minidom.parse(xmlFileName)) + diff --git a/pilas_plug.py b/pilas_plug.py new file mode 100644 index 0000000..29b369b --- /dev/null +++ b/pilas_plug.py @@ -0,0 +1,44 @@ +""" + +This command provides a plug to embed Qt. + +The X11 window ID is passed as the unique parameter. + +""" + +import os +import sys + +base = os.environ['SUGAR_BUNDLE_PATH'] + +qtpath = os.path.join(base, 'qt') +sys.path.append(qtpath) + +# del sys.path[sys.path.index('/usr/lib/python2.7/site-packages')] + +from PyQt4.QtGui import QApplication +from PyQt4.QtCore import QString +from PyQt4.QtGui import QHBoxLayout +from PyQt4.QtGui import QLineEdit +from PyQt4.QtGui import QX11EmbedWidget + +import pilas +from pilas import aplicacion + +app = QApplication(sys.argv) + +parent_window_id = int(sys.argv[1]) +screen_width = int(sys.argv[2]) +screen_height = int(sys.argv[3]) + +window = QX11EmbedWidget() +window.embedInto(parent_window_id) +window.show() + +hbox = QHBoxLayout(window) +pilas_height = 2.0 / 3 * screen_height +pilas_width = 2.0 / 3 * screen_width +pilas_widget = aplicacion.Window(parent=window, pilas_width=pilas_width, pilas_height=pilas_height) +hbox.addWidget(pilas_widget) + +sys.exit(app.exec_()) diff --git a/qt/PyQt4/Qt.so b/qt/PyQt4/Qt.so new file mode 100644 index 0000000..7eb8239 --- /dev/null +++ b/qt/PyQt4/Qt.so Binary files differ diff --git a/qt/PyQt4/QtCore.so b/qt/PyQt4/QtCore.so new file mode 100644 index 0000000..e6327c8 --- /dev/null +++ b/qt/PyQt4/QtCore.so Binary files differ diff --git a/qt/PyQt4/QtGui.so b/qt/PyQt4/QtGui.so new file mode 100644 index 0000000..7765d64 --- /dev/null +++ b/qt/PyQt4/QtGui.so Binary files differ diff --git a/qt/PyQt4/__init__.py b/qt/PyQt4/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/qt/PyQt4/__init__.py diff --git a/qt/lib/libQtCore.so.4 b/qt/lib/libQtCore.so.4 new file mode 100644 index 0000000..90d24ee --- /dev/null +++ b/qt/lib/libQtCore.so.4 Binary files differ diff --git a/qt/lib/libQtGui.so.4 b/qt/lib/libQtGui.so.4 new file mode 100644 index 0000000..6a06081 --- /dev/null +++ b/qt/lib/libQtGui.so.4 Binary files differ diff --git a/qt/sip.so b/qt/sip.so new file mode 100644 index 0000000..7e3e68a --- /dev/null +++ b/qt/sip.so Binary files differ diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..5548d9a --- /dev/null +++ b/setup.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +import sys +from setuptools import setup +from setuptools import find_packages +from pilas import pilasversion + +def error(biblioteca, web): + print "Error, no se encuentra la biblioteca '%s' (de %s)" %(biblioteca, web) + sys.exit(1) + + +try: + import Box2D +except ImportError: + error("box2d", "http://code.google.com/p/pybox2d") + + +setup( + name='pilas', + version=pilasversion.VERSION, + description='A simple to use video game framework.', + author='Hugo Ruscitti', + author_email='hugoruscitti@gmail.com', + install_requires=[ + 'setuptools', + ], + packages=['pilas', 'pilas.actores', 'pilas.dispatch', + 'pilas.ejemplos', 'pilas.motores', + 'pilas.interfaz', 'pilas.video', + 'pilas.cargador'], + url='http://www.pilas-engine.com.ar', + include_package_data = True, + package_data = { + 'images': ['pilas/data/*', 'pilas/ejemplos/data/*', + 'pilas/data/fondos/*', 'pilas/data/juegobase/*', + 'pilas/data/juegobase/data/*', + 'pilas/cargador/ejemplos', + 'pilas/cargador/data'], + }, + + scripts=['bin/pilas'], + + classifiers = [ + 'Intended Audience :: Developers', + 'Intended Audience :: Education', + 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)', + 'Natural Language :: Spanish', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Topic :: Games/Entertainment', + 'Topic :: Education', + 'Topic :: Software Development :: Libraries', + ], + ) + diff --git a/setup_xo.py b/setup_xo.py new file mode 100755 index 0000000..c24196e --- /dev/null +++ b/setup_xo.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python + +# Copyright (C) 2009, Simon Schampijer +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +from sugar.activity import bundlebuilder + +bundlebuilder.start() diff --git a/utils/actualizar_pypi.py b/utils/actualizar_pypi.py new file mode 100644 index 0000000..3068185 --- /dev/null +++ b/utils/actualizar_pypi.py @@ -0,0 +1,15 @@ +import os + +def renombrar_instalador_para_windows(): + lista = os.listdir('dist/') + instalador_windows = [item for item in lista if item.endswith(".exe") and 'linux' in item] + nombre = "dist/" + instalador_windows[0] + os.rename(nombre, nombre.replace("linux-i686", "win32")) + + +os.system("python setup.py sdist") +os.system("python setup.py bdist_wininst") +renombrar_instalador_para_windows() + +os.system("python setup.py sdist upload") +os.system("python setup.py bdist_wininst upload") diff --git a/utils/generar_resumen_api.py b/utils/generar_resumen_api.py new file mode 100644 index 0000000..028df6f --- /dev/null +++ b/utils/generar_resumen_api.py @@ -0,0 +1,59 @@ +# -*- encoding: utf-8 -*- +# Genera el contenido del archivo pilas.rst que describe +# toda la api de pilas y se agrega al manual como anexo. + +import os +import re + + +content = [] + +for x in os.walk("../pilas"): + + for filename in x[2]: + if filename.endswith(".py") and not filename.startswith("__"): + fullname = x[0] + '/' + filename[:-3] + + fullname = fullname.replace("../", "") + fullname = fullname.replace("/", ".") + + if "test" not in fullname and "__" not in fullname and "rope" not in fullname and "cargador" not in fullname: + title = fullname + content.append(title) + content.append("-" * len(title)) + + content.append("") + content.append("") + + archivo = open(os.path.join(x[0], filename), "rt") + + contenido = archivo.readlines() + + for linea in contenido: + class_name = re.match("class (.+)\((\S*)\):", linea) + + if class_name: + content.append("") + content.append("") + content.append("class **%s** (%s)" %(class_name.group(1), class_name.group(2))) + content.append("") + + method = re.match("\s*def (.+)\((\S+)\):", linea) + + if method: + argumentos = method.group(2).replace("*", "x") + argumentos = argumentos.replace("self", "") + content.append("- **%s** (%s)" %(method.group(1), argumentos)) + + + + archivo.close() + content.append("") + + + +print '\n'.join(content) +print "Actualizando el archivo 'resumen_api.rst" +archivo = open("resumen_api.rst", "wt") +archivo.write("\n".join(content)) +archivo.close() diff --git a/utils/resumen_api.pdf b/utils/resumen_api.pdf new file mode 100644 index 0000000..dd48c11 --- /dev/null +++ b/utils/resumen_api.pdf Binary files differ diff --git a/utils/resumen_api.rst b/utils/resumen_api.rst new file mode 100644 index 0000000..373b3e2 --- /dev/null +++ b/utils/resumen_api.rst @@ -0,0 +1,1183 @@ +pilas.comportamientos +--------------------- + + + + +class **Comportamiento** (object) + +- **actualizar** () +- **terminar** () + + +class **Girar** (Comportamiento) + +- **actualizar** () + + +class **Saltar** (Comportamiento) + +- **actualizar** () + + +class **Avanzar** (Comportamiento) + +- **actualizar** () + +pilas.tareas +------------ + + + + +class **Tarea** (object) + +- **ejecutar** () +- **eliminar** () + + +class **TareaCondicional** (Tarea) + +- **ejecutar** () + + +class **Tareas** (object) + +- **__init__** () + +pilas.imagenes +-------------- + + +- **cargar** (ruta) + +pilas.grupo +----------- + + + + +class **Grupo** (list) + +- **desordenar** () +- **limpiar** () + +pilas.lienzo +------------ + + + +pilas.xmlreader +--------------- + + +- **getData** () +- **makeRootNode** (xmlFileName) + +pilas.interpolaciones +--------------------- + + + + +class **Interpolacion** (object) + + + +class **Lineal** (Interpolacion) + +- **__neg__** () + +pilas.red +--------- + + +- **setup** () +- **handle** () +- **finish** () + +pilas.escenas +------------- + + + + +class **Escena** (object) + +- **__init__** () +- **iniciar** () +- **terminar** () + + +class **Normal** (Escena) + + +pilas.atajos +------------ + + +- **crear_grupo** (xk) + +pilas.fondos +------------ + + + + +class **Fondo** (pilas.actores.Actor) + + + +class **Volley** (Fondo) + +- **__init__** () + + +class **Pasto** (Fondo) + +- **__init__** () + + +class **Selva** (Fondo) + +- **__init__** () + + +class **Tarde** (Fondo) + +- **__init__** () + + +class **Espacio** (Fondo) + +- **__init__** () + + +class **Noche** (Fondo) + +- **__init__** () + + +class **Color** (Fondo) + + +pilas.control +------------- + + + + +class **Control** (object) + +- **__init__** () +- **actualizar** () +- **__init__** () +- **__str__** () + +pilas.simbolos +-------------- + + + +pilas.fisica +------------ + + + + +class **Fisica** (object) + +- **crear_bordes_del_escenario** () +- **reiniciar** () +- **cuando_suelta_el_mouse** () +- **actualizar** () +- **_procesar_figuras_a_eliminar** () +- **eliminar_suelo** () +- **eliminar_techo** () +- **eliminar_paredes** () + + +class **Figura** (object) + +- **obtener_x** () +- **obtener_y** () +- **obtener_rotacion** () +- **obtener_velocidad_lineal** () +- **detener** () +- **eliminar** () + + +class **Circulo** (Figura) + + + +class **Rectangulo** (Figura) + + + +class **ConstanteDeMovimiento** () + +- **eliminar** () + + +class **ConstanteDeDistancia** () + +- **eliminar** () + +pilas.eventos +------------- + + + +pilas.utils +----------- + + +- **es_interpolacion** (an_object) +- **obtener_ruta_al_recurso** (ruta) +- **interpolable** (f) +- **obtener_area_de_texto** (texto) + +pilas.colisiones +---------------- + + +- **__init__** () +- **verificar_colisiones** () + +pilas.fps +--------- + + +- **actualizar** () +- **obtener_cuadros_por_segundo** () + + +class **FPS** (object) + +- **actualizar** () +- **obtener_cuadros_por_segundo** () + +pilas.sonidos +------------- + + +- **cargar** (ruta) + +pilas.colores +------------- + + + + +class **Color** (object) + +- **obtener** () +- **__str__** () +- **obtener_componentes** () + +pilas.mundo +----------- + + + + +class **Mundo** (object) + +- **reiniciar** () +- **terminar** () + +pilas.pytweener +--------------- + + + + +class **Tweener** (object) + +- **hasTweens** () +- **finish** () + + +class **Tween** (object) + +- **decodeArguments** () +- **Remove** () +- **__init__** () +- **update** () +- **getRotation** () +- **complete** () + +pilas.habilidades +----------------- + + + + +class **Habilidad** (object) + +- **actualizar** () +- **eliminar** () + + +class **RebotarComoPelota** (Habilidad) + +- **eliminar** () + + +class **RebotarComoCaja** (Habilidad) + +- **eliminar** () + + +class **ColisionableComoPelota** (RebotarComoPelota) + +- **actualizar** () +- **eliminar** () + + +class **SeguirAlMouse** (Habilidad) + + + +class **AumentarConRueda** (Habilidad) + + + +class **SeguirClicks** (Habilidad) + + + +class **Arrastrable** (Habilidad) + +- **comienza_a_arrastrar** () +- **termina_de_arrastrar** () +- **_el_receptor_tiene_fisica** () + + +class **MoverseConElTeclado** (Habilidad) + + + +class **PuedeExplotar** (Habilidad) + +- **eliminar_y_explotar** () + + +class **SeMantieneEnPantalla** (Habilidad) + +- **actualizar** () + + +class **PisaPlataformas** (Habilidad) + +- **actualizar** () +- **eliminar** () + + +class **Imitar** (Habilidad) + +- **actualizar** () +- **eliminar** () + +pilas.estudiante +---------------- + + +- **__init__** () +- **eliminar_habilidades** () +- **eliminar_comportamientos** () +- **actualizar_habilidades** () +- **actualizar_comportamientos** () +- **_adoptar_el_siguiente_comportamiento** () + +pilas.pilasversion +------------------ + + + +pilas.ventana +------------- + + + +pilas.camara +------------ + + + + +class **Camara** (object) + +- **_get_x** () +- **_get_y** () + +pilas.depurador +--------------- + + + + +class **Depurador** (object) + + + +class **ModoDepurador** (object) + +- **orden_de_tecla** () + + +class **ModoPuntosDeControl** (ModoDepurador) + + + +class **ModoRadiosDeColision** (ModoDepurador) + + + +class **ModoArea** (ModoDepurador) + + + +class **ModoPosicion** (ModoDepurador) + + + +class **ModoFisica** (ModoDepurador) + + + +class **ModoInformacionDeSistema** (ModoDepurador) + + +pilas.dispatch.saferef +---------------------- + + + + +class **BoundMethodWeakref** (object) + +- **__str__** () +- **__call__** () + + +class **BoundNonDescriptorMethodWeakref** (BoundMethodWeakref) + +- **foo** () +- **__call__** () + +pilas.dispatch.dispatcher +------------------------- + + + + +class **DictObj** (object) + +- **__str__** () +- **_make_id** (target) + + +class **Signal** (object) + +- **esta_conectado** () +- **imprimir_funciones_conectadas** () + +pilas.actores.pelota +-------------------- + + + + +class **Pelota** (Actor) + + +pilas.actores.animado +--------------------- + + + + +class **Animado** (Actor) + + +pilas.actores.nave +------------------ + + + + +class **Nave** (Animacion) + +- **actualizar** () +- **eliminar_disparos_innecesarios** () +- **disparar** () +- **avanzar** () + +pilas.actores.pausa +------------------- + + + + +class **Pausa** (Actor) + + +pilas.actores.opcion +-------------------- + + + + +class **Opcion** (Texto) + +- **seleccionar** () + +pilas.actores.estrella +---------------------- + + + + +class **Estrella** (Actor) + + +pilas.actores.piedra +-------------------- + + + + +class **Piedra** (Actor) + +- **actualizar** () + +pilas.actores.aceituna +---------------------- + + + + +class **Aceituna** (Actor) + +- **normal** () +- **reir** () +- **burlarse** () +- **gritar** () +- **saltar** () + +pilas.actores.texto +------------------- + + + + +class **Texto** (Actor) + +- **obtener_texto** () +- **obtener_magnitud** () +- **obtener_color** () + +pilas.actores.banana +-------------------- + + + + +class **Banana** (Actor) + +- **abrir** () +- **cerrar** () + +pilas.actores.globoelegir +------------------------- + + + + +class **GloboElegir** (Globo) + + +pilas.actores.ejes +------------------ + + + + +class **Ejes** (Actor) + + +pilas.actores.cooperativista +---------------------------- + + + + +class **Cooperativista** (Actor) + + + +class **Esperando** (Comportamiento) + +- **actualizar** () + + +class **Caminando** (Comportamiento) + +- **__init__** () +- **actualizar** () +- **avanzar_animacion** () + + +class **Saltando** (Comportamiento) + +- **__init__** () +- **actualizar** () + +pilas.actores.dialogo +--------------------- + + +- **iniciar** () +- **obtener_siguiente_dialogo_o_funcion** () +- **_eliminar_dialogo_actual** () + +pilas.actores.disparo +--------------------- + + + + +class **Disparo** (Animacion) + +- **actualizar** () +- **avanzar** () + +pilas.actores.bomba +------------------- + + + + +class **Bomba** (Animacion) + +- **explotar** () + +pilas.actores.pizarra +--------------------- + + + + +class **Pizarra** (Actor) + + +pilas.actores.tortuga +--------------------- + + + + +class **Tortuga** (Actor) + +- **actualizar** () +- **dibujar_linea_desde_el_punto_anterior** () +- **bajalapiz** () +- **subelapiz** () +- **get_color** () + +pilas.actores.mono +------------------ + + + + +class **Mono** (Actor) + +- **sonreir** () +- **gritar** () +- **normal** () + +pilas.actores.puntaje +--------------------- + + + + +class **Puntaje** (Texto) + +- **obtener** () + +pilas.actores.utils +------------------- + + +- **insertar_como_nuevo_actor** (actor) +- **eliminar_un_actor** (actor) + +pilas.actores.mano +------------------ + + + + +class **CursorMano** (Actor) + +- **_cargar_imagenes** () + +pilas.actores.cursordisparo +--------------------------- + + + + +class **CursorDisparo** (Actor) + + +pilas.actores.actor +------------------- + + +- **obtener_centro** () +- **obtener_posicion** () +- **get_x** () +- **get_z** () +- **get_y** () +- **get_scale** () +- **get_rotation** () +- **get_espejado** () +- **get_transparencia** () +- **get_imagen** () +- **get_fijo** () +- **eliminar** () +- **destruir** () +- **actualizar** () +- **pre_actualizar** () +- **get_izquierda** () +- **get_derecha** () +- **get_abajo** () +- **get_arriba** () +- **obtener_rotacion** () +- **obtener_imagen** () +- **obtener_ancho** () +- **obtener_alto** () +- **__str__** () +- **obtener_escala** () +- **esta_fuera_de_la_pantalla** () +- **_eliminar_anexados** () + +pilas.actores.explosion +----------------------- + + + + +class **Explosion** (Animacion) + + +pilas.actores.animacion +----------------------- + + + + +class **Animacion** (Animado) + +- **obtener_velocidad_de_animacion** () +- **actualizar** () + +pilas.actores.globo +------------------- + + + + +class **Globo** (Actor) + +- **eliminar** () + +pilas.actores.temporizador +-------------------------- + + + + +class **Temporizador** (Texto) + +- **funcion_vacia** () +- **_restar_a_contador** () +- **iniciar** () + +pilas.actores.pingu +------------------- + + + + +class **Pingu** (Actor) + + + +class **Esperando** (Comportamiento) + +- **actualizar** () + + +class **Caminando** (Comportamiento) + +- **__init__** () +- **actualizar** () +- **avanzar_animacion** () + + +class **Saltando** (Comportamiento) + +- **__init__** () +- **actualizar** () + +pilas.actores.menu +------------------ + + + + +class **Menu** (Actor) + +- **activar** () +- **desactivar** () +- **seleccionar_primer_opcion** () +- **actualizar** () +- **seleccionar_opcion_actual** () +- **_deshabilitar_opcion_actual** () + +pilas.actores.moneda +-------------------- + + + + +class **Moneda** (Animacion) + + +pilas.actores.martian +--------------------- + + + + +class **Martian** (Actor) + +- **actualizar** () +- **crear_disparo** () +- **puede_saltar** () + + +class **Esperando** (Comportamiento) + +- **actualizar** () + + +class **Caminando** (Comportamiento) + +- **__init__** () +- **actualizar** () +- **avanzar_animacion** () + + +class **Saltando** (Comportamiento) + +- **actualizar** () + + +class **Disparar** (Comportamiento) + +- **actualizar** () +- **avanzar_animacion** () + +pilas.actores.caja +------------------ + + + + +class **Caja** (Actor) + + +pilas.actores.boton +------------------- + + + + +class **Boton** (Actor) + +- **desconectar_normal_todo** () +- **desconectar_presionado_todo** () +- **desconectar_sobre_todo** () +- **ejecutar_funciones_normal** () +- **ejecutar_funciones_press** () +- **ejecutar_funciones_over** () +- **activar** () +- **desactivar** () +- **pintar_normal** () +- **pintar_sobre** () + +pilas.actores.mapa +------------------ + + + + +class **Mapa** (Actor) + +- **reiniciar** () +- **eliminar** () +- **_eliminar_bloques** () + +pilas.actores.entradadetexto +---------------------------- + + + + +class **EntradaDeTexto** (Actor) + +- **_actualizar_cursor** () +- **_actualizar_imagen** () + +pilas.motores.motor_qt +---------------------- + + + + +class **BaseActor** (object) + +- **__init__** () +- **obtener_posicion** () +- **obtener_escala** () +- **obtener_transparencia** () +- **obtener_rotacion** () + + +class **QtImagen** (object) + +- **ancho** () +- **alto** () +- **centro** () +- **avanzar** () +- **__str__** () + + +class **QtGrilla** (QtImagen) + +- **ancho** () +- **alto** () +- **avanzar** () +- **obtener_cuadro** () + + +class **QtTexto** (QtImagen) + +- **ancho** () +- **alto** () + + +class **QtLienzo** (QtImagen) + +- **__init__** () + + +class **QtSuperficie** (QtImagen) + +- **limpiar** () + + +class **QtActor** (BaseActor) + +- **obtener_imagen** () +- **reproducir** () + + +class **QtBase** (motor.Motor) + +- **__init__** () +- **pantalla_completa** () +- **pantalla_modo_ventana** () +- **esta_en_pantalla_completa** () +- **alternar_pantalla_completa** () +- **centro_fisico** () +- **obtener_area** () +- **centrar_ventana** () +- **actualizar_pantalla** () +- **obtener_centro_de_la_camara** () +- **obtener_lienzo** () +- **realizar_actualizacion_logica** () +- **escala** () +- **alternar_pausa** () +- **ocultar_puntero_del_mouse** () +- **__init__** () +- **__init__** () +- **_pintar_fondo_negro** () + +pilas.motores.motor +------------------- + + + + +class **Motor** (object) + +- **__init__** () +- **ocultar_puntero_del_mouse** () +- **mostrar_puntero_del_mouse** () +- **cerrar_ventana** () +- **centrar_ventana** () +- **procesar_y_emitir_eventos** () +- **obtener_centro_de_la_camara** () + +pilas.video.video +----------------- + + + + +class **MissingOpencv** (Exception) + +- **__init__** () +- **__str__** () + + +class **DeCamara** (pilas.actores.Actor) + +- **actualizar_video** () + + +class **VideoDeArchivo** (object) + +- **obtener_imagen** () + + +class **DePelicula** (pilas.actores.Actor) + +- **actualizar_video** () + +pilas.video.webcam +------------------ + + + + +class **__camara_buffer** (object) + +- **__init__** () +- **_obtener_imagen_de_camara** () + +pilas.ejemplos.piezas +--------------------- + + + + +class **Piezas** (pilas.escenas.Normal) + + + +class **Pieza** (pilas.actores.Animado) + +- **soltar_todas_las_piezas_del_grupo** () +- **soltar** () +- **__repr__** () +- **get_x** () +- **get_y** () +- **mostrar_arriba_todas_las_piezas** () +- **mostrar_abajo_todas_las_piezas** () + +pilas.ejemplos.listaseleccion +----------------------------- + + +- **cuando_selecciona** (opcion) + +pilas.ejemplos.fisica +--------------------- + + + + +class **ColisionesFisicas** (pilas.escenas.Normal) + +- **__init__** () + +pilas.ejemplos.colisiones +------------------------- + + + + +class **Colisiones** (pilas.escenas.Normal) + +- **__init__** () +- **crear_personajes** () + +pilas.interfaz.lista_seleccion +------------------------------ + + + + +class **ListaSeleccion** (Actor) + + +pilas.interfaz.selector +----------------------- + + + + +class **Selector** (pilas.actores.Actor) + +- **_cargar_imagenes** () +- **pintar_texto** () +- **deseleccionar** () +- **seleccionar** () +- **alternar_seleccion** () + +pilas.interfaz.deslizador +------------------------- + + + + +class **Deslizador** (Actor) + + +pilas.interfaz.boton +-------------------- + + + + +class **Boton** (pilas.actores.Actor) + +- **_crear_imagenes_de_botones** () + +pilas.interfaz.ingreso_de_texto +------------------------------- + + + + +class **IngresoDeTexto** (pilas.actores.Actor) + +- **_actualizar_cursor** () +- **cualquier_caracter** () +- **solo_numeros** () +- **solo_letras** () +- **_actualizar_imagen** () + +pilas.data.juegobase.ejecutar +----------------------------- + + -- cgit v0.9.1