Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pyglet. How to change picture(animate) for vertices dynamically. OpenGL

Environment:

Python: 3.6.6
pyglet version: 1.3.2

Code base:

abstract_model.py

import pyglet


def get_texture_group(file, order_group_index):
    texture = pyglet.image.load(file).texture
    order_group = pyglet.graphics.OrderedGroup(order_group_index)
    return pyglet.graphics.TextureGroup(texture, order_group)


class AbstractModel(object):

    def _create_as_vertex(self):
        v_x = self.cell_data.get("x") * 32
        v_y = self.cell_data.get("y") * -1 * 32

        texture_group = self.map_type_iamge.get(self.cell_data.get("t"))
        x_offset = self.x_offset * self.scale

        x, y, z = v_x + x_offset, v_y, self.z
        x_ = (texture_group.texture.width * self.scale + x_offset + v_x)
        y_ = (texture_group.texture.height * self.scale + v_y)

        tex_coords = ('t2f', (0, 0, 1, 0, 1, 1, 0, 1))

        self.vertices = self.batch.add(
            4, pyglet.gl.GL_QUADS,
            texture_group,
            ('v3f', (x, y, z,
                     x_, y, z,
                     x_, y_, z,
                     x, y_, z)),
            tex_coords)

    def _animate(self, dt):
        # lets assume that I have list of pyglet.graphics.TextureGroup
        # and they should somehow be drawn one after other
        print("I need change image. dt=", dt, self)
        pyglet.clock.schedule_once(self._animate, 1)

ground3d.py

import os
import pyglet

import settings
from models import abstract_model


GROUND_DIR = os.path.join(settings.STATIC_DIR, "ground")

order_group_index = 0

map_type_iamge = {
    1: abstract_model.get_texture_group(os.path.join(GROUND_DIR, "w1.png"), order_group_index),
    2: abstract_model.get_texture_group(os.path.join(GROUND_DIR, "t1.png"), order_group_index),
    1001: abstract_model.get_texture_group(os.path.join(GROUND_DIR, "t1_direction.png"), order_group_index),
}


class Ground3D(abstract_model.AbstractModel):

    def __init__(self, cell_data, batch):

        self.batch = batch
        self.cell_data = cell_data
        self.map_type_iamge = map_type_iamge
        self.scale = 1
        self.x_offset = 0
        self.z = 0
        self.entity = None

        self._create_as_vertex()
        pyglet.clock.schedule_once(self._animate, 1)

Explanation:

I have models(just flat rect for an example) which should be placed on 3 dimensions. And these models should be animated, like picture_1, after second picture_2, ... etc.
As I understood from my previous question using pyglet.sprite.Sprite() in 3D batch is not a good idea.

Question:

How I can change pictures(using TextureGroup or any other approaches) on self.vertices?

Or which arroach/classes I use use to implement it. I can't find any examples for such (as for my simple vision) usual case as animation for some flat models in 3 dimensions.

There are many example about rotating/moving/resizing of vertices, but how to build a correct question(is animation aspect) for getting answer in google - I don't know.

PS: If you, reader, have any usefull links on this subject(for pyglet or just for OpenGL) I would be very appreciated you share this link(s) in comment.

like image 202
Yuriy Leonov Avatar asked Oct 17 '22 11:10

Yuriy Leonov


1 Answers

Texture coordinates.

You should have a single texture atlas for all frames of all different things that are animated ever.

Preferably, everything should have same animation speed and same amount of frames all the time.

Let's say there's two sprites that have 2 frames for entire animation, and they are stored in 64x64 texture atlas. (EDIT: sorry for ambiguity, 64x64 PIXELS, just because it could imply that we have 64x64 tile atlas, same everywhere else where I mention this)

Now, you need to have a global timer with global value which indicates current animation frame, not game frame. It should be independent of framerate.

Said value should be updated every once in a while at your desired speed like this:

current_frame = (current_frame + 1) % animation_length

Since we have 2 frames in this example, it will turn out like this:

# init
animation_length = 2
current_frame = 0

# updates:
current_frame = (0 + 1) % 2 # 1 % 2 -> 1
current_frame = (1 + 1) % 2 # 2 % 2 -> 0
...

Now, you need to update UV's of all your sprites only when the frame changes.

UV's start from left right and go from 0 to 1 (as far as I remember, for the sake of this example, they do, shhh).

Since we have 2 frames each, we can calculate "tiles" in the UV coordinates like this:

tile_width = 1.0 / frames # 2 frames each, width will be 0.5
tile_height = 1.0 / sprites # 2 sprites each, height will be 0.5 too, perfect

Now, on first frame, you generate your UV's like normal, you just take vertical ID or something, and use tile_height * sprite_id to get current V coordinate, and your U is calculated like tile_width * current_frame.

This assumes that you already have sprite batching so what you do is go over every sprite on update, and basically just recalculate new UV's with new frame, meaning all sprites change their frame to the next one, yay!

If you want to have systems that are independent of eachother, say, very slow animations for some, and faster for others, you'll need different sprite batches or proper calculation on from where to where you need to update UV's in vertex buffer array. Everything else is exactly the same, except now current_frame won't be global but rather contained, preferebly in some list or separate object that manages timers for animations.

You don't need to change anything in your shaders, they just need right UV's for the frames and you're set.

By the way, this is very basic, you could apply some logic yourself so you could instead have 16x16 grid of 32x32 pixels in your texture, each line of sprites having 4 different animations, these could be either sprite's states (attack, run, etc), but how you do it is entirely on you, most importantly, get it to work. Goodluck.

But if you do it the way I said, then state will be another integer, and UV for state, assuming all states have exactly the same width, it would be like this:

state_width = 1 / states
tile_width = 1 / (states * frames_per_state)
U = state_width * current_state + tile_width * current_frame

Now, one issue arises, player could start his animation at the last attack frame.

It's normal, entities with actions should all have individual timers, what I described above, is for tons of sprites that are just background, like grass. Now when you divided it up, you could have a proper way to reset current frame to 0 when new state is assigned.

If your entities are objects, you could write proper methods that recalculate UV's every time you rebuild the sprite batch using those sprites, and then timers itselves could be contained in objects.

We need to draw something? Check animation state, has it changed, no? Send UV's that were calculated before, otherwise, wait a bit, we need to recalculate, only then add those to VBO, and well, render your thing, in the end, it will appear as if you have animations, even though really, it's just a simple, but great UV manipulation.

Goodluck.

like image 189
Purple Ice Avatar answered Nov 15 '22 05:11

Purple Ice