Showing posts with label Python. Show all posts
Showing posts with label Python. Show all posts

Scrolling Camera System

We created a tilemap but what if our map's size is bigger than our window's size. We are controlling our player character in the game and this character has to go everywhere where it can. It can go everywhere already with the current state of the game, however, we aren't able to see that. That's why we have to implement a scrolling camera system on the game as a solution. This system will be focused on the player and follow it. But there is an illusion on it. Actually, the camera doesn't follow the player, when we moved the player, the tilemap will be updated on the screen according to the player's position. 

Let's visualize this mechanic below. The player is represented by red dot, the rectangle with a blue border represents a game display just the user can see and there is a map generated with brown and green rectangles. When the player was moving up, actually, the map moving down. As you see can:
Scrolling Camera System
If the player moves down, the map will be moved up in fact:
Scrolling Camera System
This situation is valid when the player moved through the right and left:
Scrolling Camera System
Everything make sense, I mean we have to calculate positions of every object in the game according to target which is also player.Let's start with typing Camera Class:
import pygame
from pygame.math import Vector2

class Camera:
    def __init__(self, target, width, height):
        self.width = width
        self.height = height
        self.camera = Vector2(target.pos.x, target.pos.y)
        self.target = target
        self.offset = Vector2(0,0)
We are going to use a vector for the camera, also we need to screen's width and height. We will use width and height values for centering the target and camera on the screen. A vector in math has a length and direction. We created a camera vector that will contain the position of our camera and assigned position of target object, initially. The target variable contains our player what we want to focus on with the camera. The offset vector will be used for setting the position of each object in the game.

We are adding a method called scroll for calculating offset value and the tilemap:
    def scroll(self):
        self.pointTarget = self.target.pos - self.camera
        self.camera += self.pointTarget
        self.offset = -self.camera + Vector2(self.width/2, self.height/2)
We calculated pointTarget vector that will be point to the target vector. Because when the player moved, the camera's position must be updated according to the target. So, we will calculate the distance between the camera and the target and this distance that is pointTarget will be added to camera for to the getting position of player. Finally, we calculate the offset value according to the camera vector. If you realize, Vector2(self.width/2, self.height/2) is added to the offset in addition, to do centering to the player on the screen. 

Lets use it in main.py:
import pygame
import random

from player import Player
from tilemap import *
from camera import Camera

...

player = Player(sprites_group, (0,0), (25,25), (0,0,255))

map_data = generate_map(1024, 1024)

camera = Camera(player, 640, 480)

e = Entity(sprites_group, (32,32), (32,32), (255,0,255))

def main():
    ...
    while running:
        ...

        camera.scroll()
        
        # draw
        screen.fill((255,255,255))
        draw_map(screen, map_data, camera)
        for sprite in sprites_group:
             screen.blit(sprite.image, (sprite.rect.topleft + camera.offset))
        
        ...
camera the object is created and passed player object. In the game loop, we are calculating offset with camera.scroll() and we will update positions of all sprites in the game according to camera vector with for loop and render them at the same time. But this scrolling hasn't affected the tilemap yet. As a solution, we have to change to draw_map function a little bit, and actually, we do the same thing that we do for the sprites. Every tile's position has to be updated with offset value as in the code below:
def draw_map(screen, map_data, camera = None):
    ...
                    
    if camera:
        for row in range(MAP_HEIGHT):
            for col in range(MAP_WIDTH):
                screen.blit(textures[map_data[row][col]],
                            (col*TILE_SIZE + camera.offset.x, row*TILE_SIZE + camera.offset.y))
    ... 
So we did it, we can around everywhere on the map.
Scrolling Camera System

Well, maybe I should fill the background with black color. 👀

You are able to reach full of source code on the Github repo.
Devamını Oku »

Generating Tile Map

Our game is an RPG game and it will be a tile-based game. In this tutorial, we are going to create a map. There are some editors for creating tilemap and using them on the game. However, I'm not thinking to use it. I mentioned the first tutorial for this game, I said, "it will be a survival game like Minecraft, Terraria, etc.". When we created a new game on these games, they generated a map randomly. In that case, we are going to generate our map randomly. It's just the beginning, of course, we should optimize this map with a lot of things.

Well, we need tiles for creating a map. I'm going to create a dictionary that will contain tiles. I'm not going to draw the texture of these tiles. These tiles will be represented by filled color surfaces. Let's create a new file called tilemap.py and add it tiles:
# dimension of each tiles
TILE_SIZE = 32

# texture of colors
YELLOW  = (255, 255, 0)
RED     = (255, 0, 0)
BLUE    = (0 , 0, 255)
GREEN   = (0, 255, 0)
BROWN   = (160, 82, 45)
We will use these colors to creating texture. I'm going to type a function called create_texture:
def create_texture(color):
    image = pygame.Surface((TILE_SIZE,TILE_SIZE))
    image.fill(color)
    return image
Let's create our textures in the dictionary:
# 0x0 -> grass
# 0xb -> dirt
textures = {
    0x0 : create_texture(GREEN),
    0xb : create_texture(BROWN)
}
I would like to visualize how we generate map randomly:
generating tile map
According to the above image, We have two functions that are generate_map() function and draw_map() function. generate_map() function returns a list which contains random tile from tiles each cell. The data, we got from generate_map() function, are passed to draw_map() function and this function use this map data for drawing to the screen. If you examine codes of functions, you realize we used nested loops for generating data and drawing map. Let's code it:
# generate with tiles randomly
def generate_map(width, height, tilesize = TILE_SIZE):
    map_data = []
    for i in range(height // tilesize):
        map_data.append([])
        for j in range(width // tilesize):
            rand_index = random.randint(0,1)
            # convert to hex from string value
            tile = int(hex(tiles[rand_index]), 16)
            map_data[i].append(tile)
    return map_data


def draw_map(screen, map_data):
    MAP_HEIGHT = len(map_data) 
    MAP_WIDTH = len(map_data[0])
    for row in range(MAP_HEIGHT):
        for col in range(MAP_WIDTH):
            screen.blit(textures[map_data[row][col]],
                        (col*TILE_SIZE, row*TILE_SIZE))        
I am going to use these functions in main.py:
...

map_data = generate_map(640, 480)

def main():
    running = True
    # the game loop
    while running:
        ...
        
        # draw
        screen.fill((255,255,255))
        draw_map(screen, map_data)
        sprites_group.draw(screen)
        ...
So our tile map looks like it:
Generating Tile Map
We got it, however, it's not exactly what we want. Because everything is just a mess and we just use two tiles, think about what happen if we try to add another tiles to the map. The random concept is cause of this. We have to look another alternatives. 

You can reach full of source code from this link


Devamını Oku »

Player Movement

When we are playing a game, most of the time we have to move the player. If we want to move the player, we should add a move function to the Player class. The move function contains two arguments as dx and dy. The d stands for delta. But it's not not enough for moving the player. We need to update function for changing the player's position according to updated rect's positions. Well, it's not for just updating position, in addition, we will get events for the player object from the update function. Let's create these two function in the Player class:
class Player(pygame.sprite.Sprite):
    def __init__(self, sprites_group, pos, dim, col):
        ...

    def update(self):
        self.get_event()

    def get_event(self):
        keys = pygame.key.get_pressed()
        if keys[pygame.K_w]:
            self.move(0, -5)
        if keys[pygame.K_s]:
            self.move(0, +5)
        if keys[pygame.K_a]:
            self.move(-5, 0)
        if keys[pygame.K_d]:
            self.move(+5, 0)

    def move(self, dx, dy):
        self.rect.x += dx
        self.rect.y += dy
So, we typed get_event function for when we pressed the keys about direction, the player's position will be changed via move function. We are adding get_event function to the update function. The update function is already defined in Sprite class so it related sprite_group. If we update sprites_group, all update function of sprites will run. So we just add sprites_group.update().
def main():
    running = True
    # the game loop
    while running:
        ...
        
        # draw
        ...

        # update
        sprites_group.update()
        pygame.display.flip()
If we run the program and move the player:
Player movement is so fast. It's about FPS. FPS stands for frame per second. I'm not touching this concept in this post. To sum up, our game is frame-based game and FPS is depending on computer speed currently. We are able to get over this problem with tick() for a while. We will give an argument as an integer, for instance, if we pass 60 to tick function, that means every second just show 60 frames on the screen. So, we are creating our clock object for using time that is a module of pygame. It gives info about in-game time:
 ...
# we initialize pygame module
pygame.init()

clock = pygame.time.Clock()

...
Finally, we are adding clock.tick(60) statement in the game loop:
...

def main():
    running = True
    # the game loop
    while running:
        clock.tick(60)

        ...
As a result:

Player Movement



Devamını Oku »

Adding Player Object to the RPG Game

We will add a player which will be controlled by us to the game. In the previous post, we created the structure of our game code primarily. If we come to the main point, we are going to type a Player class for the main character. In pygame, there is a sprite class that provides useful features like update, draw, etc. So basically, the Sprite class is the parent class for our player object and other game objects. 

Let's created Player class in file named player.py. The Player class inherits from the Sprite class:
import pygame

class Player(pygame.sprite.Sprite):
    def __init__(self, pos, dim, col):
        pygame.sprite.Sprite.__init__(self)

        self.image = pygame.Surface([w,h])
        self.image.fill(col)

        self.rect = self.image.get_rect()
        self.rect.center = (x,y)
pygame.sprite.Sprite.__init__(self), we are going to call constructor of inherited class which is Sprite. The image attribute is object's surface and it's actually view of object. The rect attribute is rectangle object that created from the image attribute. We can control position of the object via the rect attribute. 

The important stage is how to add this object to the game. Pygame provides a nice feature called sprite groups for this. Sprite group gives us to managment multiple objects from one point. This group is a container.
...

screen = pygame.display.set_mode((640, 480))

sprites_group = pygame.sprite.Group()

def main():
    ...
So, We are going to update Player sprite class like this:
import pygame

class Player(pygame.sprite.Sprite):
    def __init__(self, sprites_group, pos, dim, col):
        self.groups = sprites_group
        pygame.sprite.Sprite.__init__(self, self.groups)

        self.image = pygame.Surface([w,h])
        self.image.fill(col)

        self.rect = self.image.get_rect()
        self.rect.center = (x,y)
We are going to create our player character with Player class and draw it via sprites group:
import pygame
import random

from player import Player

...

sprites_group = pygame.sprite.Group()

player = Player(sprites_group, screen.get_rect().center, (25,25), (0,0,255))

def main():
    running = True
    # the game loop
    while running:
        ...
        
        screen.fill((255,255,255))
        sprites_group.draw(screen)
        
        # update
        pygame.display.flip()

if __name__ == "__main__":
    main()

pygame.quit()
The result of the our game is: 
Adding player object to the game


This tutorial's source code on the Github.
Devamını Oku »

The Game Loop

What is a loop? The loop's meaning of this is repeating itself. This operation could depend on a specific value or it can take forever. We could want it works 40 times or 600 times or forever. Well, the game loop will be run forever until we close the game. The game loop is the most important structure that will keep our game active. Everything about the game will be updated, drawn in the game loop. If the game loop starts, the game is on, if it ends, the game is done. The game loop is the thing that keeps alive our game. 

Let's get to work. I'm going to create a new folder for the game and it will contain the file named main.py. This file contains us game code. We are going to use pygame module. If you don't have any idea about it, you can look at its documentation.

The first thing we should import to pygame:
import pygame
After that, we are going to initialize it:
pygame.init()
Create a surface represent our game window. 640x480 is the game resolution and they are optional as values. We have to pass resolution argument as tuple:
screen = pygame.display.set_mode((640, 480))
I'm going to create main function which contains the game loop.
def main():
    running = True
    # the game loop
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
        
        screen.fill((255,255,255))
        pygame.display.flip()
The variable called running is our key of the game loop. If it is true, the loop run and all the statements in the loop run againg and againg until the loop ends. The for loop is the event handling cycle. I typed this loop because if I dont, pygame doesn't let the program works as well. So, we get the event about quit option and if user wants to close program, the running variable is will be false and the game loop will be done. screen.fill((255,255,255)) statement fills the screen with white color. (255,255,255) is the rbg value and represent of white color. pygame.display.flip() is used for update all the display for every frame. In addition, there is a pygame.display.update() but it's not the same exactly.
if __name__ == "__main__":
    main()

pygame.quit()
What is this statement if __name__ == "__main__":. It provides modules we created can be executed as main file. It gives test oppurtinity fast. Well, it's not really important thing for now. We couldn't use it. pygame.quit() off pygame module.

Run this program and we will get a white window on the screen. It's okay but we can't see clearly how to works the game loop. So, let's make an example about it. I'm going to draw a circle on the screen. We can use draw functions of pygame. That function is pygame.draw.circle(screen, color, position, radius). I want to draw the red circle on the center of the screen:
def main():
    running = True
    # the game loop
    while running:
        for event in pygame.event.get():
            ...
            
        # draw
        screen.fill((255,255,255))
        pygame.draw.circle(screen, (255,0,0), screen.get_rect().center, 20)
        
        # update
        pygame.display.flip()
And we run this code. We are getting the view of the game:
Pygame Game Programming

That's good. Let's give a movement to this red circle. I'm going to use random module. For each frame, the circle's position will be changed by randint function and gives movement motion to the circle:
def main():
    running = True
    # the game loop
    while running:
        for event in pygame.event.get():
            ...
            
        # draw
        screen.fill((255,255,255))
        pygame.draw.circle(screen, (255,0,0), (random.randint(0, 640), 240), 20)
        
        # update
        pygame.display.flip()
Don't remember import random statement. The result what we got it:
Pygame Animation Circle

That's enough for this post. The next post will be about adding a player to the game and we are going to control it in the game. 

The full of source code:
import pygame
import random

# we initialize pygame module
pygame.init()

# create a surface represent our window
screen = pygame.display.set_mode((640, 480))

def main():
    running = True
    # the game loop
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
        # draw
        screen.fill((255,255,255))
        pygame.draw.circle(screen, (255,0,0), (random.randint(0, 640), 240), 20)
        
        # update
        pygame.display.flip()

if __name__ == "__main__":
    main()

pygame.quit()
Devamını Oku »

Introduction to Game Programming

 This blog series will be about game programming. We are going to use pygame to game programming. So, we need pygame module to programming the game, if you haven't it then install it before. Pygame is python module can be used tools which are about graphic, sound, etc. Why are we using pygame? Because it provides easy and fast usage. We don't move with a lot of complexity.

My plan is to create a game that will be really useful for learning game programming and its concepts. In addition, I'm not a professional game programmer, and actually, I will continue to learn with these articles. Probably, it is a kind of learning technics.

This game will be a survival RPG game like Minecraft, Terraria, etc. That's classic for the tutorial. This game will be a tile-based game and of course, it is two-dimensional and we are going to use pixel-art for its game art. Reader, at this point it is you, should know enough stage about these requirements:

  • Python programming language
  • Object-oriented programming (oop)
  • Math (basic level is enough for the beginning) 
I think everything is clear for now. I have no plan about how should be the game? This post journey is going to be as impromptu. Let's start to program our game.
Devamını Oku »

Drawing a Triangle - Modern OpenGL with Python

The first thing we will do is a creating program object. A program object is an empty object that we will attach shaders to it.

...
FPS = 60

# we created program object 
program = glCreateProgram() 

while True:
	...
So, we could pass Vertex Shader and Fragment Shader to the program object. These shaders are actually stages of a rendering pipeline. We will type them with GLSL like the C programming language. These shaders determine what we will draw on the screen step by step. We will use two shader types; vertex shader and fragment shader. The vertex shader is the first step on the pipeline that defines vertices of shape that will be drawn on the screen. For instance, if we want to draw a triangle then we need three vertices. The fragment shader is the last state on the rendering pipeline that gives color to every pixel which is in the region between vertices. It will out result on the screen. So I typed these shader's codes as follows:
...

VERTEX_SHADER_SOURCE = '''
    #version 330 core
    layout (location = 0) in vec3 aPos;
    
    void main()
    {
        gl_Position = vec4(aPos, 1.0)
    }
'''

FRAGMENT_SHADER_SOURCE = '''
    #version 330 core

    out vec4 fragColor;
    void main()
    {
        fragColor = vec4(1.0f, 0.0f, 0.0f, 1.0f)
    }
'''

# we created program object 
program = glCreateProgram() 

...
I will talk about the shader codes in detail later. We are trying to draw a triangle and we gave red color this triangle in FRAGMENT_SHADER_SOURCE. Let's use them shader objects which we will create:
...
# we created program object 
program = glCreateProgram()

# we created vertex shader
vertex_shader = glCreateShader(GL_VERTEX_SHADER)
# we passed vertex shader's source to vertex_shader object
glShaderSource(vertex_shader, VERTEX_SHADER_SOURCE)
# and we compile it
glCompileShader(vertex_shader)

...
We will do the same thing for fragment_shader. So if it is done, we will attach these shaders to the program object.
...

# attach these shaders to program
glAttachShader(program, vertex_shader)
glAttachShader(program, fragment_shader)

while True:
	...
Finally, we will link this program object as follows:
...

# link the program
glLinkProgram(program)

while True:
	...
So we can start drawing progress. OpenGL's coordinate system is different from others. For example, pygame display's origin point is in top-left, But if we put a point on (0,0) origin in OpenGL, this point shows itself in the middle of the screen. The other thing is we should know, x-axis and y-axis can values that are between 1 and -1.

Let's create vertices data in a list that will define the position of the triangle's vertices:
...

#  (x, y, z)
vertices = [
    -0.5, -0.5, 0.0,
    0.5, -0.5, 0.0,
    0.0, 0.5, 0.0,
]
vertices = (GLfloat * len(vertices))(*vertices)
...
We have to pass this vertex data to OpenGL. Therefore, we have to use VBO (vertex buffer object). The VBO contains data like this:
...

# create vbo object
vbo = None
vbo = glGenBuffers(1, vbo)

# enable buffer(VBO)
glBindBuffer(GL_ARRAY_BUFFER, vbo)

# send the data  
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW)

...
Now, OpenGL will take this data. However, OpenGL couldn't know what's going on with this data exactly. We have to declare this data to OpenGL structurally. Because this data may contain position, color, etc. like kind of data. Thus, VAO(vertex array object) appears to bring a solution to this problem. To sum up, we use VAO for explaining how to use data to OpenGL.

For explaining data to OpenGL, We use three tokens for using each attribute in data. For instance, we might want to get texture coordinates as an attribute from data.
  • How many elements in the attribute?
  • What is the type of each element in the attribute?
  • Where is the beginning of the attribute?
Probably, it's not a really good explanation. Let's review this code:
# create vao object
vao = None
vao = glGenVertexArrays(1, vao)

# enable VAO and then finally binding to VBO object what we created before.
glBindVertexArray(vao)

# we activated to the slot of position in VAO (vertex array object)
glEnableVertexAttribArray(0)

# explaining to the VAO what data will be used for slot 0 (position slot) 
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), c_void_p(0))
Let's look at the below image:
vertex data in opengl
Now we know how to use data in VAO. First thing, we should enable VAO then we could use data in ARRAY_BUFFER. There is just one attribute called position or slot 0. So we just enabled slot 0. So, we declare the position data in data to VAO via vertexAttribPointer. Let's look at the below image to understanding:
vertexattribpointer usage
Finally, we can draw the triangle on the screen.
    ...

    glClear(GL_COLOR_BUFFER_BIT)
    
    glUseProgram(program)
    glBindVertexArray(vao)
    glDrawArrays(GL_TRIANGLES, 0, 3)
    
    pygame.display.flip()

    ...
If we want to VAO then we have to enabled firstly. We already enabled this VAO but if there are more VAO. It doesn't work properly. Therefore we should enable the VAO before we draw.
glDrawArrays arguments:
  • GL_TRIANGLES is a primitive type we use when we wanted to drive a triangle.
  • The 0 value is the index of the enabled VBO's array.
  • The 3 value is the meaning of three points which will be rendered
And it's the result:
a triangle with opengl
This is full of the code:

import pygame
from OpenGL.GL import *
from ctypes import sizeof, c_void_p

pygame.init()
display = pygame.display.set_mode((800, 600), pygame.DOUBLEBUF|pygame.OPENGL)
clock = pygame.time.Clock()
FPS = 60


VERTEX_SHADER_SOURCE = '''
    #version 330 core
    layout (location = 0) in vec3 aPos;
    
    void main()
    {
        gl_Position = vec4(aPos, 1.0);
    }
'''

FRAGMENT_SHADER_SOURCE = '''
    #version 330 core

    out vec4 fragColor;
    void main()
    {
        fragColor = vec4(1.0f, 0.0f, 0.0f, 1.0f);
    }
'''

#  (x, y, z)
vertices = [
    -0.5, -0.5, 0.0,
    0.5, -0.5, 0.0,
    0.0, 0.5, 0.0,
]
vertices = (GLfloat * len(vertices))(*vertices)

# we created program object 
program = glCreateProgram()

# we created vertex shader
vertex_shader = glCreateShader(GL_VERTEX_SHADER)
# we passed vertex shader's source to vertex_shader object
glShaderSource(vertex_shader, VERTEX_SHADER_SOURCE)
# and we compile it
glCompileShader(vertex_shader)


# we created fragment shader
fragment_shader = glCreateShader(GL_FRAGMENT_SHADER)
# we passed fragment shader's source to fragment_shader object
glShaderSource(fragment_shader, FRAGMENT_SHADER_SOURCE)
# and we compile it
glCompileShader(fragment_shader)

# attach these shaders to program
glAttachShader(program, vertex_shader)
glAttachShader(program, fragment_shader)

# link the program
glLinkProgram(program)

# create vbo object
vbo = None
vbo = glGenBuffers(1, vbo)

# enable buffer(VBO)
glBindBuffer(GL_ARRAY_BUFFER, vbo)

# send the data  
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW)

# create vao object
vao = None
vao = glGenVertexArrays(1, vao)

# enable VAO and then finally binding to VBO object what we created before.
glBindVertexArray(vao)

# we activated to the slot of position in VAO (vertex array object)
glEnableVertexAttribArray(0)

# explaining to the VAO what data will be used for slot 0 (position slot) 
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), c_void_p(0))

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            quit()

    glClearColor(1.0, 0.6, 0.0, 1.0)
    glClear(GL_COLOR_BUFFER_BIT)

    glUseProgram(program)
    glBindVertexArray(vao)
    glDrawArrays(GL_TRIANGLES, 0, 3)
    
    pygame.display.flip()
    clock.tick(FPS)
Devamını Oku »

Introduction to Modern OpenGL with Python

There are already a lot of sources about Modern OpenGL. But I'm gonna keep going on it as basically. Probably, this post doesn't seem like really scientific stuff. Why I prefer Python for this. Because it's just simple and We have to learn a lot of things for a short period. A programming language is just a tool, well it's for just learning something.

I'm gonna use Pygame which Python's module. Pygame will be an interface that we will communicate with OpenGL. 

We need to OpenGL. Let's install it:

pip install PyOpenGL
So I typed code that below:
import pygame
from OpenGL.GL import *

pygame.init()
display = pygame.display.set_mode((800, 600), pygame.DOUBLEBUF|pygame.OPENGL)
clock = pygame.time.Clock()
FPS = 60

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            quit()
    
    glClearColor(1.0, 0.6, 0.0, 1.0)
    glClear(GL_COLOR_BUFFER_BIT)

    pygame.display.flip()
    clock.tick(FPS)
And that's result what we got:

That's good. There are a few different additions in the basic example of pygame. I'm not going deep into OpenGL in this post. But I will strive to be simple and understandable in the next posts that include this series.
Devamını Oku »

Adding Tilemap Component to ECS

So, today I started to make a tilemap component and map system. I already typed TileComponent class last week. I could read the file with txt extension successfully. I edited it though. TileComponent takes the following arguments:

  • File source (example: map.txt)
  • Tileset image (it consists of multiple tiles)
  • Tile size (example: 32px)
I used to Aseprite for making an instance tileset as 96x96. Each tile's dimension should be 32x32 according to this we will pass tile size argument as 32. In addition, this tileset contains 9 tiles. A tileset base grass image to use:
Tileset
1. Tileset image as 96x96
I will use this tileset to tilemap's view. For a natural look, each tile will be located randomly on the map. The render operation just works for part of the image. We do this with the blit function. 
self.app.screen.blit(self.entity.components["TILEMAP_COMPONENT"].tileset, #image
(
    col * self.entity.components["TILEMAP_COMPONENT"].tilesize, # x
    row * self.entity.components["TILEMAP_COMPONENT"].tilesize  # y
), 
(
    self.entity.components["TILEMAP_COMPONENT"].tilesize * random.randint(0,2), # random tile as part of tileset | x-axis
    self.entity.components["TILEMAP_COMPONENT"].tilesize * random.randint(0,2), # random tile as part of tileset | y-axis
    self.entity.components["TILEMAP_COMPONENT"].tilesize,                       # tilesize
    self.entity.components["TILEMAP_COMPONENT"].tilesize                        # tilesize
))

This is how the map.txt file looks like:

If tile is b which means "base" tile then draw random tile part of tileset on the screen.

I created a map entity with TileMapComponent for my game like this:
self.map_entity = Entity(self, [
    TileMapComponent(MAP_1, BASE_TILESET, 32)
])
And then we can use this entity as a map with MapSystem if we process it:
self.mapSystem = MapSystem(self, self.entityManager.entities)
self.mapSystem.process()
The result I got it:
Pygame Tilemap

Well, TileMapComponent and Map system work properly. However, entity sprites doesn't seem normal. I removed screen.fill() line code but I have to, otherwise the tilemap won't be seen because of screen.fill(). mapSystem.process worked once. It isn't in game loop. Let's add it in the loop:
Pygame Tilemap

This is chaos. But it's useful. If you notice, there is one "2" character in map.txt so because of that we have a gap and the white color sprite leaves a scar on map. We have to stop changing tiles for each loop frame. It's easy just remove the random seed from the loop. But first, I want to see FPS of creepy game:
Pygame Tilemap
I ran into some problems while I was trying to addding the fps text to the game. Apparently, I didn't use pygame.display.flip() function correctly. That's should be like this:

def run(self):
    self.playing = True
    while self.playing:
        self.dt = self.clock.tick(60) / 1000
        self.events()
        self.update()
        self.draw()
        self.show_fps()
        pygame.display.flip()
My fps is fixed below 60. If I remove 60 value from tick parameter:
Pygame Tilemap

We already know that but the problem is about performance. I think if we stop the changing of tileset's images then we can get better values of the FPS, we have to stop anyway. I created a random_seed in TileMapComponent:
class TileMapComponent(Component):
		...
        
        self.random_seed = []
        self.random_length = self.tileWidth * self.tileHeight
        for i in range(self.random_length):
            self.random_seed.append(random.randint(0,2))
Now we can use this list of random_seed in MapSystem:
self.app.screen.blit(self.entity.components["TILEMAP_COMPONENT"].tileset, #image
(
    col * self.entity.components["TILEMAP_COMPONENT"].tilesize, # x
    row * self.entity.components["TILEMAP_COMPONENT"].tilesize  # y
), 
(
    self.entity.components["TILEMAP_COMPONENT"].tilesize * self.entity.components["TILEMAP_COMPONENT"].random_seed[self.counter], # random tile as part of tileset | x-axis
    self.entity.components["TILEMAP_COMPONENT"].tilesize * self.entity.components["TILEMAP_COMPONENT"].random_seed[self.counter + 1], # random tile as part of tileset | y-axis
    self.entity.components["TILEMAP_COMPONENT"].tilesize,                       # tilesize
    self.entity.components["TILEMAP_COMPONENT"].tilesize                        # tilesize
))
Let's look the result:
Pygame Tilemap

Yes, it looks better now. Not bad but not enough, I'm already thinking about tilemap editor for this. This is just a try. I think that's enough for blog post. 
Devamını Oku »