5

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.

Yuriy Leonov
  • 536
  • 1
  • 9
  • 33
  • 1
    I am not sure if i correctly understood your question. You have rectangles and you want to move them in 3d space and apply textures on them? – Attila Toth Oct 19 '18 at 08:51
  • @AttilaToth How to move it - I understand. Via changing vertexes like `self.entity.vertices[0] = 22`. I do not understand how to change texture. – Yuriy Leonov Oct 19 '18 at 09:08
  • @AttilaToth I found that `self.entity.tex_coords` could be changed as well, but it will change only current `texture_group`. There could be way to create a big image and change tex_coords accordingly. + There is `pyglet.image.Texture3D` but I can't figure out how it should be used with `pyglet.graphics.TextureGroup` and how to "switch" between pictures in them. – Yuriy Leonov Oct 19 '18 at 09:12

1 Answers1

3

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.

Purple Ice
  • 439
  • 1
  • 3
  • 11
  • Thanks for your post. Yeah, I fidured out that 1) for animation one big file(sprite) is more preferred than several small files. 2) There is no way to share one object in memory for all vertices, which describes texture position and anyway required to iterate throught all objects like you metioned `go over every sprite on update`. I've implemented such approach with `pyglet.image.ImageGrid()` and object, which contains required texture coords for particular object's type. But I hope where is somewhere some trick which could be more convenient. – Yuriy Leonov Oct 22 '18 at 14:15
  • 1) you can do it over several textures, the problem is that you will haveto send different batches, or you have to bind multiple textures and then tell sprite which one to sample from, it adds more complexity to shaders and might have negative performance impact. 2) That shouldn't be an issue, when you render all sprites that are visible on screen, you still add all of their vertices into VBO to render a batch, don't you? Each sprite is a quad, so every frame that you render, you go over all sprites, collecting vertex data and adding it to the VBO because their position might have changed – Purple Ice Oct 22 '18 at 15:13
  • What I mean is, you update vertex VBO every time because sprite might have moved so it needs to be resent again, do same with UV's :) – Purple Ice Oct 22 '18 at 15:13
  • It has sence if vertices changes their coordinates. But in my case they change only texture(weird, but it's my case, and count of elements could be more than 200k). Anyway I undestoon that inerate throught elements in batch it's common way. – Yuriy Leonov Oct 22 '18 at 15:43
  • You definitely should store your textures into a single image instead of having separate textures. For example, your textures can be 512x512 pixels, and your atlas could be 8192x8192 pixels, you could store 256 textures, if your game used only less than 256 textures, you could bind your atlas ONCE during lifetime of a program and then just change UV's to render different stuff. In case of sprites in a 2D game, they would be a lot smaller and you could have a lot of sprites all in one texture. – Purple Ice Oct 27 '18 at 09:43