My EPQ project - the creation of a 2D game engine in Python (a sort of pygame, if you will) except that it supports drawing rotated images and shapes, as well as rotated rectangular and circular hitboxes.
Features:
- Graphics Windows, Surfaces
- Collision Detection
- Support for rectangles (and hitboxes) rotated in any direction
- Python-esque code
- DLLs for both 32bit and 64bit windows to allow for faster maths calculations in the background
- FPS control
import engine
. This will directly import everything necessary for your project.
Display(width, height, title="Better Pygame")
. Optionally one can also add the arguments x
and y
to set the coordinates of the window onto the screen.
Display.update()
- refresh the display.
Display.fill(colour)
- fill the display, with colour being an rgb length 3 tuple.
Display.blit(surface, src = None, dest = None, angle = 0)
- copy a surface onto the display. Optional args:
src
: A pair of tuples dictating the topleft and bottomright corners of the area of the surface to blit from.dest
: A tuple dicating the centre of the destination where the surface will be blit.angle
: the angle at which the surface will be drawn, where positive = anti-clockwise.
Surface(width, height, angle = 0, depth = 32)
- create a new Surface object which you can draw on. Optional args:
angle
: the angle at which the surface will be drawn if it is drawn onto a display, where positive = anti-clockwise. If one takes a rotated surface and draws it rotated more onto a display, the angles will add up.depth
: the depth of the surface is the level onto which it is drawn - higher or lower number means it will be drawn above or below other surfaces.mask
: Do not edit this unless you know what you are doing. An rgb tuple of form (r, g * 256, b * 256 ^ 2, a * 256 ^ 3).
blit(surface, src = None, dest = None, angle = 0)
- blit a surface onto current surface. Works in the same way as Display.Surface().
fill(colour)
- fill the surface with a colour of the form rgb, or the form rgba.
fill_circle(colour)
- fill as large a circle as possible within the surface, with a colour of the form rgb, or the form rgba.
rotate(angle)
- change the default rotation of the surface by angle
. Enables one to rotate a surface without re-initialising it.
get_rect()
- returns a Rect() collider object with coords and size of surface.
get_circle()
- returns a Circle() collider object with coords of the centre of the surface, and a size as large as possible which still fits in the surface.
draw_circle(surface, x, y, rad, colour = (0, 0, 0, 0), Filled = True)
- draw a circle onto the chosen surface at chosen coordinates. Optional args:
rad
- the radius of the circle to draw.colour
- a tuple of the form rgb or rgba dictating the colour of the circle to drawFilled
- bool, whether or not to fill in the circle or draw only the outline.
draw_rect(surface, x, y, w, h, angle = 0, colour = (0, 0, 0, 0))
- draw a rectangle onto the chosen surface at chosen coordinates. Optional args:
colour
- a tuple of the form rgb or rgba dictating the colour of the rectangle to drawangle
- the angle to draw the rectangle at.
Sprite(x = 0, y = 0, angle = 0)
- create a new Sprite object.
Sprite objects have the following inbuilt methods/variables:
image
- the image pasted onto the screen using the sprite.draw() methodcollider
- the hitbox of the sprite, to be used when checking collision between sprites - will be updated if the sprite is moved, or rotated.x, y, angle
- self explanatory - angle is anticlockwise.+=
- it is possible to move a sprite by adding (or subtracting) a tuple to (or from) it, for examplesprite += (10, 0)
orsprite -= (5, 6)
.rotate(angle)
- rotate the sprite, image and collider all by a certain angle.draw(surface)
- draw the image of the sprite onto the chosen surface. Will draw it with anglesprite.angle
mentioned above, at coordsx, y
above.get_rect(self)
- finds rect object for the sprite's image, as per the functionsurface.get_rect
.get_circle(self)
- similarly, finds maximum circle object for sprite's image.update()
- this method can be changed when making one's own child class - it is called automatically in sprite groups, so it is preferable to keep the name the same.
Group()
- Creates a new sprite group. These can be used to update multiple sprites at once.
add(*sprites)
- add as many sprites to the group as you want, one after the other (the same syntax as if you wanted to print multiple variables at once: add(sprite1, sprite2, sprite3)
).
update()
- run the update()
method for all sprites within the group.
draw(surface)
- draws all sprites within the group to the chosen surface. Again, the rotation and position of each sprite will be dictated by its current x, y, angle
variables.
There are three types of colliders - Rect()
, Circle()
and PixelArray()
(although PixelArray hasn't been properly intergrated yet, it is still supported by the collision()
function).
Note: Colliders and objects generally use centred coordinates, so initialising an object at (0, 0) will put its centre, not its topleft corner, at those coords.
collision(obj1, obj2)
- detects whether two collider objects are touching, and returns True
if they are, False
if they are not.
Rect(x, y, width, height, angle = 0)
- pretty self explanatory. Creates a newRect()
collider with coords (x, y) and width and height as written. An optionalangle
parameter allows for the object to be created already rotated in some way.update(x, y, width, height, angle = 0)
- allows you to essentially move the object in any way you want without needing to actually create a new object.rotate(angle)
- rotate theRect
collider by a certain angle.- Like sprites it is possible to move any of these objects using
+=
or-=
. shiftx(num)
- move the rectangle horizontally bunum
pixels.shifty(num)
- as above but for height.move(coords)
- "teleport" the object to any location written as an (x, y) tuple.
Circle(x, y, rad)
- create a newCircle()
collider with coords (x, y) and radius as written.update(x, y, rad)
- move and/or resize the circle collider in any way you want without needing to reinitialise the object.- Once again
+=
and-=
can be used for movement, using tuples. move(coords)
- "teleport" the circle collider to any coords written as an (x, y) tuple.
An Event()
is, well, an event which takes place whenever a key is pressed or the mouse is moved or basically any event happens. The codes for the letters and numbers, as well as the space bar, is simply ord("char")
for the character you wish. Events have the following properties:
type
- the type of event it is. The types of events are:
- AUDIODEVICEADDED
- AUDIODEVICEREMOVED
- CONTROLLERAXISMOTION
- CONTROLLERBUTTONDOWN
- CONTROLLERBUTTONUP
- CONTROLLERDEVICEADDED
- CONTROLLERDEVICEREMOVED
- CONTROLLERDEVICEREMAPPED
- FINGERMOTION
- FINGERDOWN
- FINGERUP
- KEYDOWN
- KEYUP
- JOYAXISMOTION
- JOYBALLMOTION
- JOYHATMOTION
- JOYBUTTONDOWN
- JOYBUTTONUP
- JOYDEVICEADDED
- JOYDEVICEREMOVED
- MOUSEMOTION
- MOUSEDOWN
- MOUSEUP
- MOUSEWHEEL
- MULTIGESTURE
- QUIT
- SYSWMEVENT
- TEXTEDITING
- TEXTINPUT
- USEREVENT
- WINDOWEVENT
- These can be accessed like so:
engine.EVENTNAME
. key
- If a key was pressed or unpressed, the code of that key.event
- in case you want access to the original SDL2 event which was returned.x
andy
for those events which would use those.button
- for controller or mouse pressed or releasesclicks
- the amount of times the button was pressed for the event, like a single or double click.
get_events()
- returns an array with all events which have happened since this function was last called. It is recommended to call this function whether or not the events will be used.get_pressed()
- returns an array with1
s for every key code which is currently pressed/held down and0
s for those which are not so.is_pressed(key)
- returnsTrue
if the inputted key code is currently pressed andFalse
if it is not.- There is a list of key codes here.
Clock()
- returns aClock()
object. Its main functionality is itstick(freq)
method, which should be called at the start of the game loop. Its job is to limit the framerate, sotick(30)
will limit the FPS to 30.exit()
- force close the app. Should be used as the way to exit the game once everything else is done.