2

Am using custom shader on my game, and i must because of performance. Now i am at the point where i want to bind touch events to my particles/Ufo so that i can decide what to do when someone touches them, but i don't know how i can calculate their width and height. I am currently able to tell where a touch event happened but my collide_point function always return False because i don't have the correct Width and Height of my game particle's. collide_point function require particle's right and top and particle's right and top requires particle's width and height to work. In the documentation it is said that

the width and the height property is subject to layout logic

but am not using any Layout, am using Widget instead. How can i calculate my game particle's width and height. Below is the code

from __future__ import division
from collections import namedtuple
import json
import math
import random
from kivy import platform
from kivy.app import App
from kivy.base import EventLoop
from kivy.clock import Clock
from kivy.core.image import Image
from kivy.core.window import Window
from kivy.graphics import Mesh
from kivy.graphics.instructions import RenderContext
from kivy.uix.widget import Widget
from kivy.utils import get_color_from_hex
import base64

UVMapping = namedtuple('UVMapping', 'u0 v0 u1 v1 su sv')

GLSL = """
---vertex
$HEADER$

attribute vec2  vCenter;
attribute float vScale;

void main(void)
{
    tex_coord0 = vTexCoords0;
    mat4 move_mat = mat4
        (1.0, 0.0, 0.0, vCenter.x,
         0.0, 1.0, 0.0, vCenter.y,
         0.0, 0.0, 1.0, 0.0,
         0.0, 0.0, 0.0, 1.0);
    vec4 pos = vec4(vPosition.xy * vScale, 0.0, 1.0)
        * move_mat;
    gl_Position = projection_mat * modelview_mat * pos;
}

---fragment
$HEADER$

void main(void)
{
    gl_FragColor = texture2D(texture0, tex_coord0);
}

"""

with open("game.glsl", "wb")  as glslc:
    glslc.write(GLSL)

def load_atlas():
    atlas = json.loads('''{"game-0.png": {"Elien": [2, 26, 100, 100]}}''') 

    tex_name, mapping = atlas.popitem()
    data = '''iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAACJklEQVR4nO3dy1ICQRAF0YLw/39ZVxMBGCjEMF23JvOsXPgounMaN61VQrtsH3x3TqFfLv9/ykdcq9z8RKv25Lro5yiUAcAZAJwBwBkAnAHAGQCcAcAZAJwBwBkAnAHAGQCcAcAZAJwBwBkAnAHAGQCcAcAZAJwBwBkAnAHAfXUPsNM79ydWXbYZZVoAey7MPH6tQdScAI64KbV9T3QI6QGsuCKHDiH5l8DVd1aRd2QTT4DOjcCdBmknQMpTmDLH4ZICSFv0tHkOkRJA6mKnzvUxCQGkL3L6fLskBKBG3QFMebqmzPm2zgCmLeq0eV/SfQKoWVcAU5+mqXM/5QkA1xHA9Kdo+vx3PAHgDADOAOBWB3CW98+zvA5PADoDgDMAOAOAMwA4A4AzADgDgDMAuNUBnOXCxVlehycAnQHAGQBcRwDT3z+nz3/HEwCuK4CpT9HUuZ/yBIDrDGDa0zRt3pd0nwBTFnXKnG/rDkDNEgJIf7rS59slIYCq3EVOnetjUgKoylvstHkOkRRAVc6ip8xxuMS/E7gtfsflC8zGb9JOgFurNwO3+VWZJ8CtFacBcuM36QFsjggBvfGbKQFsHjfNfxix07QAHrmpOyX/EqgFDADOAOAMAM4A4AwAzgDgDADOAOAMAM4A4AwAzgDgDADOAOAMAM4A4AwAzgDgDADOAOAMAM4A4AwA7lrl7YpE7okkSZIkSZIkSZIkSZIkSZIkSZL+9AMvSSThyPfOhQAAAABJRU5ErkJggg=='''
    with open(tex_name, "wb")  as co:
        co.write(base64.b64decode(data))
    tex = Image(tex_name).texture
    tex_width, tex_height = tex.size

    uvmap = {}
    for name, val in mapping.items():
        x0, y0, w, h = val
        x1, y1 = x0 + w, y0 + h
        uvmap[name] = UVMapping(
            x0 / tex_width, 1 - y1 / tex_height,
            x1 / tex_width, 1 - y0 / tex_height,
            0.5 * w, 0.5 * h)

    return tex, uvmap


class Particle:
    x = 0
    y = 0
    size = 1

    def __init__(self, parent, i):
        self.parent = parent
        self.vsize = parent.vsize
        self.base_i = 4 * i * self.vsize
        self.reset(created=True)

    def update(self):
        for i in range(self.base_i,
                       self.base_i + 4 * self.vsize,
                       self.vsize):
            self.parent.vertices[i:i + 3] = (
                self.x, self.y, self.size)

    def reset(self, created=False):
        raise NotImplementedError()

    def advance(self, nap):
        raise NotImplementedError()


class GameScreen(Widget):
    indices = []
    vertices = []
    particles = []

    def __init__(self, **kwargs):
        Widget.__init__(self, **kwargs)
        self.canvas = RenderContext(use_parent_projection=True)
        self.canvas.shader.source = "game.glsl"

        self.vfmt = (
            (b'vCenter', 2, 'float'),
            (b'vScale', 1, 'float'),
            (b'vPosition', 2, 'float'),
            (b'vTexCoords0', 2, 'float'),
        )

        self.vsize = sum(attr[1] for attr in self.vfmt)
        self.texture, self.uvmap = load_atlas()

    def on_touch_down(self, touch):
        for w in self.particles:
            if w.collide_point(*touch.pos):
                w.reset() #Not Working
        return super(GameScreen, self).on_touch_down(touch) 

    def on_touch_move(self, touch):
        for w in self.particles:
            if w.collide_point(*touch.pos):
                w.reset() #Not Working
        return super(GameScreen, self).on_touch_move(touch) 

    def make_particles(self, Ap, num):
        count = len(self.particles)
        uv = self.uvmap[Ap.tex_name]

        for i in range(count, count + num):
            j = 4 * i
            self.indices.extend((
                j, j + 1, j + 2, j + 2, j + 3, j))

            self.vertices.extend((
                0, 0, 1, -uv.su, -uv.sv, uv.u0, uv.v1,
                0, 0, 1,  uv.su, -uv.sv, uv.u1, uv.v1,
                0, 0, 1,  uv.su,  uv.sv, uv.u1, uv.v0,
                0, 0, 1, -uv.su,  uv.sv, uv.u0, uv.v0,
            ))

            p = Ap(self, i)
            self.particles.append(p)

    def update_glsl(self, nap):
        for p in self.particles:
            p.advance(nap)
            p.update()

        self.canvas.clear()

        with self.canvas:
            Mesh(fmt=self.vfmt, mode='triangles',
                 indices=self.indices, vertices=self.vertices,
                 texture=self.texture)

class Ufo(Particle):
    plane = 2.0
    tex_name = 'Elien' 
    texture_size = 129
    right = top = 129

    def reset(self, created=False):
        self.plane = random.uniform(2.0, 2.8)
        self.x = random.randint(15, self.parent.right-15)
        self.y = self.parent.top+random.randint(100, 2500)
        self.size = random.uniform(0.5, 1.0) #every particle must have a random size
        self.top = self.size * self.texture_size 
        self.right = self.size * self.texture_size 

    def collide_point(self, x, y):
        '''Check if a point (x, y) is inside the Ufo's axis aligned bounding box.'''
        with open('TouchFeedback.txt', 'wb') as c:
            c.write(str(x)+', '+str(y)) 

        return self.x <= x <= self.right and self.y <= y <= self.top

    def advance(self, nap):
        self.y -= 100 * self.plane * nap
        if self.y < 0:
            self.reset()

class Game(GameScreen):
    def initialize(self):
        self.make_particles(Ufo, 20)

    def update_glsl(self, nap):
        GameScreen.update_glsl(self, nap)

class GameApp(App):
    def build(self):
        EventLoop.ensure_window()
        return Game()

    def on_start(self):
        self.root.initialize()
        Clock.schedule_interval(self.root.update_glsl, 60 ** -1)

if __name__ == '__main__':
    Window.clearcolor = get_color_from_hex('111110')
    GameApp().run()
glibdud
  • 7,550
  • 4
  • 27
  • 37

2 Answers2

0

Try using collide_widget()

Snippets

def on_touch_down(self, touch):
    if self.collide_point(*touch.pos):
        for w in self.particles:
            if self.collide_widget(w):
                w.reset()
                return True
    return super(GameScreen, self).on_touch_down(touch) 

Widget class » collide_widget

collide_widget(wid)

Check if another widget collides with this widget. This function performs an axis-aligned bounding box intersection test by default.

Parameters:

wid: Widget class Widget to test collision with.

Returns:
bool. True if the other widget collides with this widget, False otherwise.

ikolim
  • 15,721
  • 2
  • 19
  • 29
0

I think you are not calculating/updating your top and right properties of your Ufo objects. Also, since your GL code considers (x,y) to be the center of your circle, but Kivy considers (x,y) to be the lower left corner of an object, you need to keep that in mind. In order to correctly calculate collide_point(), I have added left and bottom properties to your Ufo, and use those properties to calculate collisions. Here is an updated version of your code with those changes:

from __future__ import division
import kivy
from kivy.config import Config
from kivy.graphics.context_instructions import Color
from kivy.graphics.vertex_instructions import Rectangle
from kivy.lang import Builder

Config.set('modules', 'monitor', '')


from collections import namedtuple
import json
import math
import random
from kivy import platform
from kivy.app import App
from kivy.base import EventLoop
from kivy.clock import Clock
from kivy.core.image import Image
from kivy.core.window import Window
from kivy.event import EventDispatcher
from kivy.graphics import Mesh
from kivy.graphics.instructions import RenderContext
from kivy.properties import NumericProperty
from kivy.uix.widget import Widget
from kivy.utils import get_color_from_hex
import base64

UVMapping = namedtuple('UVMapping', 'u0 v0 u1 v1 su sv')

GLSL = """
---vertex
$HEADER$

attribute vec2  vCenter;
attribute float vScale;

void main(void)
{
    tex_coord0 = vTexCoords0;
    mat4 move_mat = mat4
        (1.0, 0.0, 0.0, vCenter.x,
         0.0, 1.0, 0.0, vCenter.y,
         0.0, 0.0, 1.0, 0.0,
         0.0, 0.0, 0.0, 1.0);
    vec4 pos = vec4(vPosition.xy * vScale, 0.0, 1.0)
        * move_mat;
    gl_Position = projection_mat * modelview_mat * pos;
}

---fragment
$HEADER$

void main(void)
{
    gl_FragColor = texture2D(texture0, tex_coord0);
}

"""

with open("game.glsl", "wb")  as glslc:
    glslc.write(GLSL.encode())

def load_atlas():
    atlas = json.loads('''{"game-0.png": {"Elien": [2, 26, 100, 100]}}''')

    tex_name, mapping = atlas.popitem()
    data = '''iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAACJklEQVR4nO3dy1ICQRAF0YLw/39ZVxMBGCjEMF23JvOsXPgounMaN61VQrtsH3x3TqFfLv9/ykdcq9z8RKv25Lro5yiUAcAZAJwBwBkAnAHAGQCcAcAZAJwBwBkAnAHAGQCcAcAZAJwBwBkAnAHAGQCcAcAZAJwBwBkAnAHAfXUPsNM79ydWXbYZZVoAey7MPH6tQdScAI64KbV9T3QI6QGsuCKHDiH5l8DVd1aRd2QTT4DOjcCdBmknQMpTmDLH4ZICSFv0tHkOkRJA6mKnzvUxCQGkL3L6fLskBKBG3QFMebqmzPm2zgCmLeq0eV/SfQKoWVcAU5+mqXM/5QkA1xHA9Kdo+vx3PAHgDADOAOBWB3CW98+zvA5PADoDgDMAOAOAMwA4A4AzADgDgDMAuNUBnOXCxVlehycAnQHAGQBcRwDT3z+nz3/HEwCuK4CpT9HUuZ/yBIDrDGDa0zRt3pd0nwBTFnXKnG/rDkDNEgJIf7rS59slIYCq3EVOnetjUgKoylvstHkOkRRAVc6ip8xxuMS/E7gtfsflC8zGb9JOgFurNwO3+VWZJ8CtFacBcuM36QFsjggBvfGbKQFsHjfNfxix07QAHrmpOyX/EqgFDADOAOAMAM4A4AwAzgDgDADOAOAMAM4A4AwAzgDgDADOAOAMAM4A4AwAzgDgDADOAOAMAM4A4AwA7lrl7YpE7okkSZIkSZIkSZIkSZIkSZIkSZL+9AMvSSThyPfOhQAAAABJRU5ErkJggg=='''
    with open(tex_name, "wb")  as co:
        co.write(base64.b64decode(data))
    tex = Image(tex_name).texture
    tex_width, tex_height = tex.size

    uvmap = {}
    for name, val in mapping.items():
        x0, y0, w, h = val
        x1, y1 = x0 + w, y0 + h
        uvmap[name] = UVMapping(
            x0 / tex_width, 1 - y1 / tex_height,
            x1 / tex_width, 1 - y0 / tex_height,
            0.5 * w, 0.5 * h)

    return tex, uvmap


class Particle(EventDispatcher):
    # x = 0
    # y = 0
    x = NumericProperty(0)
    y = NumericProperty(0)
    size = 1

    def __init__(self, parent, i):
        super(Particle, self).__init__()
        self.parent = parent
        self.vsize = parent.vsize
        self.base_i = 4 * i * self.vsize
        self.reset(created=True)

    def update(self):
        for i in range(self.base_i,
                       self.base_i + 4 * self.vsize,
                       self.vsize):
            self.parent.vertices[i:i + 3] = (
                self.x, self.y, self.size)

    def reset(self, created=False):
        raise NotImplementedError()

    def advance(self, nap):
        raise NotImplementedError()


class GameScreen(Widget):
    indices = []
    vertices = []
    particles = []

    def __init__(self, **kwargs):
        Widget.__init__(self, **kwargs)
        self.canvas = RenderContext(use_parent_projection=True)
        self.canvas.shader.source = "game.glsl"

        self.vfmt = (
            (b'vCenter', 2, 'float'),
            (b'vScale', 1, 'float'),
            (b'vPosition', 2, 'float'),
            (b'vTexCoords0', 2, 'float'),
        )

        self.vsize = sum(attr[1] for attr in self.vfmt)
        self.texture, self.uvmap = load_atlas()

    def on_touch_down(self, touch):
        for w in self.particles:
            if w.collide_point(*touch.pos):
                w.reset() #Not Working
        return super(GameScreen, self).on_touch_down(touch)

    def on_touch_move(self, touch):
        for w in self.particles:
            if w.collide_point(*touch.pos):
                w.reset() #Not Working
        return super(GameScreen, self).on_touch_move(touch)

    def make_particles(self, Ap, num):
        count = len(self.particles)
        uv = self.uvmap[Ap.tex_name]

        for i in range(count, count + num):
            j = 4 * i
            self.indices.extend((
                j, j + 1, j + 2, j + 2, j + 3, j))

            self.vertices.extend((
                0, 0, 1, -uv.su, -uv.sv, uv.u0, uv.v1,
                0, 0, 1,  uv.su, -uv.sv, uv.u1, uv.v1,
                0, 0, 1,  uv.su,  uv.sv, uv.u1, uv.v0,
                0, 0, 1, -uv.su,  uv.sv, uv.u0, uv.v0,
            ))

            p = Ap(self, i)
            self.particles.append(p)

    def update_glsl(self, nap):
        for p in self.particles:
            p.advance(nap)
            p.update()

        self.canvas.clear()
        self.canvas.before.clear()  # temporary

        with self.canvas.before:  # temporary code block
            for p in self.particles:
                Rectangle(pos=(p.left, p.bottom), size=(p.size*p.texture_size, p.size*p.texture_size))

        with self.canvas:
            Mesh(fmt=self.vfmt, mode='triangles',
                 indices=self.indices, vertices=self.vertices,
                 texture=self.texture)


class Ufo(Particle):
    plane = 2.0
    tex_name = 'Elien'
    texture_size = 129
    right = NumericProperty(129)
    top = NumericProperty(129)
    left = NumericProperty(0)
    bottom = NumericProperty(0)

    def reset(self, created=False):
        self.plane = random.uniform(2.0, 2.8)
        self.size = random.uniform(0.5, 1.0) #every particle must have a random size
        self.x = random.randint(15, self.parent.right-15)
        self.y = self.parent.top+random.randint(100, 2500)

    def collide_point(self, x, y):
        '''Check if a point (x, y) is inside the Ufo's axis aligned bounding box.'''
        return self.left <= x <= self.right and self.bottom <= y <= self.top

    def advance(self, nap):
        self.y -= 100 * self.plane * nap
        if self.y < 0:
            self.reset()

    def on_x(self, instance, new_x):
        self.right = new_x + self.size * self.texture_size / 2.0
        self.left = new_x - self.size * self.texture_size / 2.0

    def on_y(self, instance, new_y):
        self.top = new_y + self.size * self.texture_size / 2.0
        self.bottom = new_y - self.size * self.texture_size / 2.0

class Game(GameScreen):
    def initialize(self):
        self.make_particles(Ufo, 20)

    def update_glsl(self, nap):
        GameScreen.update_glsl(self, nap)

class GameApp(App):
    def build(self):
        EventLoop.ensure_window()
        return Game()

    def on_start(self):
        self.root.initialize()
        Clock.schedule_interval(self.root.update_glsl, 60 ** -1)

if __name__ == '__main__':
    Window.clearcolor = get_color_from_hex('111110')
    GameApp().run()

I have also adding drawing the Ufo bounding box using canvas.before. This is just to visualize the clickable area for each Ufo, and can be easily removed.

John Anderson
  • 35,991
  • 4
  • 13
  • 36
  • John you are incredible.! – user11333258 Apr 10 '19 at 06:45
  • Am facing another problem, that for loop which is being executed on every touch is killing my performance. From 55-60fps when no touch event happened to 12-35fps when any touch event is fired. Is there any way is can directly use EventDispatcher on my Ufo class. I tried it but i doesn't seems working. Any advice. – user11333258 Apr 10 '19 at 12:14
  • Did you remove the logging? `with open('TouchFeedback.txt', 'wb') as c: c.write(str(x)+', '+str(y)) ` – John Anderson Apr 10 '19 at 16:41
  • yes i do but am still getting low fps when i touch the screen i think that looping is a problem. Is it possible to use EventDispatcher directly on my Ufo class.? i tried it but i get no error it just don't work – user11333258 Apr 10 '19 at 18:30
  • The other problem is that on_touch_down is not working fine i will have to touch a particle several times to reset it on_touch_move in the other hand works just fine – user11333258 Apr 10 '19 at 18:37
  • On my system, I am getting 78-80 fps (using kivy.modules.monitor) for your code regardless of how quickly I continually click or move with mouse button depressed. You can fairly easily make `Particle` extend `EventDispatcher` (I did it and used it to automatically update `right` and `top` when `x` or `y` changed), but I don't see any performance change. Is there something specific in `EventDispatcher` that you want to use? – John Anderson Apr 10 '19 at 18:55
  • on desktop it is cazy fast but on android its not btw my game is for mobile. if you don't mind please shade some light on how i can integrate EventDispatcher on my Ufo class – user11333258 Apr 10 '19 at 19:17
  • i want to receive touch events directly from my Ufo class – user11333258 Apr 10 '19 at 19:19
  • You could do that by making the `Ufo` class extend `Widget` (or any class that extends `Widget`), and adding each `Ufo` to the `GameScreen` using `add_widget()`. You could then eliminate the `on_touch_down()` and `on_touch_move()` methods from `GameScreen`, then the super class of `GameScreen` (`Widget`) would handle sending the touch events to the `Ufo` instances. But the `Widget` class has the identical loops that you currently have in your `GameScreen`, so I would not expect any performance improvement. – John Anderson Apr 11 '19 at 00:38
  • I've been tinkering with my code a bit, to see why my game's `on_touch_move` method is working perfectly fine while my game's `on_touch_down` method doesn't. At first i thought the problem is with my mobile touch screen but sadly the problem still presist even on Samsumg Galaxy s9, so in any way the problem must be the code and not a mobile touch screen. Unfotunately i discovered that if i touch `exactly on the center of the particle`( which i should), the `collide_point` method always return `False` but if i touch `a bit right from the center` or `a bit top from the center` of any of my ga – user11333258 Apr 17 '19 at 11:22
  • game's particle it does work (`collide_point` method always return `True`). Is there anything that can be done to improve my game's `collide_point` precision?, I want to do something when i touch any visible part of my game's particle. – user11333258 Apr 17 '19 at 11:25
  • I post multiple comments because my comment was too long – user11333258 Apr 17 '19 at 11:26
  • I have updated my answer to address that issue. The problem is the `GL` interpretation of `(x,y)` of `Ufo` as its center, as opposed to the `Kivy` interpretation of `(x,y)` as the lower left corner of a `Ufo`. – John Anderson Apr 17 '19 at 13:40