Entities, Sprites and Animations - Madour/pyNasNas GitHub Wiki
In a game, there are multiple Entities that interract with other Entities. Each Entity is represented by a Sprite. A Sprite can have multiple Animations composed of Animation Frames.
If we take the example of a platformer game : the player can walk, jump, crouch, die. Those are Animations of the player Sprite. The player can also hit monsters, collect coins, etc. Player, monsters and coins are Entities, and each one has a sprite with multiple animations.
Entities
An Entity is an object with specific properties, it has :
- a name
- a sprite
- animations
- an animation state
- a position (x and y)
- a collision box
- an update() method
All of these are implemented in the class ns.BaseEntity
which should
be inherited by your custom entities.
The framework also provides a basic ns.PlatformerEntity
class with gravity
and simple collision detection that you can use, other type of entities
will be added in the future.
Your are encouraged to write your own entities for your specific needs.
Sprites
ns.Sprite
is a very simple class to describe a spritesheet.
It has a name, a texture (sf.Texture) and animations (a dictionary of Anim objects).
Animations
The class ns.Anim
is used to describe one animation. Thus, a ns.Sprite
can have
multiple ns.Anim
.
In each ns.Anim
, you can have multiple ns.AnimFrame
which describe one frame of an
animation.
An animation frame is described by :
- A texture rectangle : a
ns.Rect
of the texture - A frame duration : duration of the frame in milliseconds
- A frame origin (optional) : the origin the frame (top left corner by default)
Example by code
For this example, I will use a free sprite : https://rvros.itch.io/animated-pixel-hero. Let's focus on idle and run animations: img/adventurer.png Download the sprite sheet and save it as 'adventurer.png' in the assets folder
First, let's describe the sprite:
adventurer_spr = ns.Sprite(
name="Adventurer",
texture=ns.Res.adventurer,
anims= {
"idle": ns.Anim(),
"run": ns.Anim()
}
)
Note: every Sprite must have a "idle" animation, it is the default anim_state when the entity is created
Now, you need to describe the two animations. img/adventurer_frames.png
Black rectangles are idle animation frames, and blue ones are for run animation. The red dots are the origin of each frame. We need all of these informations to describe an animation.
A way to find the positions and sizes of the rectangle is to use the selection tool of an image editor: img/sprite_frame_trick.png
This way, you can directly read the position and size of the selection.
Now, we can fill the two animations in our code :
import NasNas as ns
adventurer_spr = ns.Sprite(
name="Adventurer",
texture=ns.Res.adventurer,
anims= {
"idle": ns.Anim(
frames=[
ns.AnimFrame(ns.Rect((14, 7), (19, 29)), 300, (11, 29)),
ns.AnimFrame(ns.Rect((66, 6), (17, 30)), 300, (9, 30)),
ns.AnimFrame(ns.Rect((115, 6), (19, 30)), 300, (10, 30)),
ns.AnimFrame(ns.Rect((163, 7), (20, 29)), 300, (12, 29))
],
loop=True # loop is True by default
),
"run": ns.Anim(
[
ns.AnimFrame(ns.Rect((17, 45), (20, 28)), 100, (12, 28)),
ns.AnimFrame(ns.Rect((66, 46), (20, 27)), 100, (15, 27)),
ns.AnimFrame(ns.Rect((116, 48), (20, 25)), 100, (15, 25)),
ns.AnimFrame(ns.Rect((167, 45), (23, 28)), 100, (13, 28)),
ns.AnimFrame(ns.Rect((216, 46), (20, 27)), 100, (14, 27)),
ns.AnimFrame(ns.Rect((266, 48), (20, 25)), 100, (15, 25)),
],
)
}
)
Finally, let's create an Entity with this sprite and add it to the scene. Don't forget to update() your entities ! Otherwise you will not see the animation.
Here is the full code:
import NasNas as ns
from sfml import sf
class Game(ns.App):
def __init__(self):
super().__init__(title="My SFML Project", w_width=800, w_height=450,
v_width=250, v_height=140.6, fps=60)
ns.Res.load()
# creating a sprite
adventurer_spr = ns.Sprite(
name="Adventurer",
texture=ns.Res.adventurer,
anims= {
"idle": ns.Anim(
frames=[
ns.AnimFrame(ns.Rect((14, 7), (19, 29)), 300, (11, 29)),
ns.AnimFrame(ns.Rect((66, 6), (17, 30)), 300, (9, 30)),
ns.AnimFrame(ns.Rect((115, 6), (19, 30)), 300, (10, 30)),
ns.AnimFrame(ns.Rect((163, 7), (20, 29)), 300, (12, 29))
],
loop=True # loop is True by default
),
"run": ns.Anim(
[
ns.AnimFrame(ns.Rect((17, 45), (20, 28)), 100, (12, 28)),
ns.AnimFrame(ns.Rect((66, 46), (20, 27)), 100, (15, 27)),
ns.AnimFrame(ns.Rect((116, 48), (20, 25)), 100, (15, 25)),
ns.AnimFrame(ns.Rect((167, 45), (23, 28)), 100, (13, 28)),
ns.AnimFrame(ns.Rect((216, 46), (20, 27)), 100, (14, 27)),
ns.AnimFrame(ns.Rect((266, 48), (20, 25)), 100, (15, 25)),
],
)
}
)
self.player = ns.BaseEntity(adventurer_spr)
self.player.position = (50, 50)
self.scene.add_layer(ns.Layer("ent", self.player), order=0)
self.game_camera.scene = self.scene
self.debug = False
def event_handler(self, event):
if event == sf.Event.KEY_PRESSED:
if event['code'] == ns.Keyboard.ESCAPE:
self.window.close()
def update(self):
if ns.Keyboard.is_key_pressed(ns.Keyboard.SPACE):
self.player.anim_state = "run"
else:
self.player.anim_state = "idle"
# dt is the delta time, Entities need dt to update
self.player.update(self.dt)
As you can see, it is not very readable to define all your
sprites in Game.__init__
, it is better to define them in
another file (e.g. sprites.py
) and import them.
<- [Scenes, Layers and Camera]] ](/Madour/pyNasNas/wiki/[[Tiled-Maps) ->