I'm making a 3D FPS in Ursina, and I'd like to have my arm skin with the weapon as an image, not an actual 3D model. Does anyone know how to do this ? I tried with Animate from the documentation, but this loads an image as an object in my scene.
What I could do is define a quad with player as parent, and positional arguments, so that it follows me and I see it at the right place, but even this wouldn't work as the texture argument doesn't accept gifs.
So, does anyone know how to do that ?
You can load an animated gif with Animation() which creates an entity. As part of the interface you'll want to attach it to the UI:
from ursina import *
app = Ursina()
gif = 'animation.gif'
a = Animation(gif, parent=camera.ui)
a.scale /= 5 # adjust right size to your needs
a.position = (0.5, -0.5) # lower right of the screen
app.run()
You will need the imageio Python package installed to load gifs.
OLD ANSWER
The Entity you use for the gun has to be anchored to the interface using its parent parameter. Here's an example for a Minecraft-style hand (just a block really):
class Hand(Entity):
def __init__(self):
super().__init__(
parent=camera.ui,
model='cube',
position=Vec2(0.5, -0.3),
scale=(0.2, 0.2, 0.5),
rotation=(150, -30, 0)
)
The important part is parent=camera.ui
Sadly, I cannot seem to find a way to play gifs. I know, #Jan Wilamowski updated his answer but, in my project, that does NOT work. You CAN show static images on this screen, though, by making a quad entity with an image texture, as shown:
class yourimage(Entity):
def __init__(self):
super().__init__(
parent=camera.ui,
model='quad',
#size, position, and rotate your image here
texture = 'yourimage')
yourimage()
Related
I am not fully understanding how to use canvas correctly for images with animations.
See the attached snippet, where I load an animated icon into an Image and do both:
(1) add_widget the Image
(2) create a Rectangle canvas instruction with a texture = Image's texture
The Image animates
The Rectangle texture does not
I have read through all of the Kivy manual and read through Image and Canvas and I get the idea that Image is a nice high level class with all of this image animation handling and Canvas is more of a raw low-level drawing canvas.
So here is my question - what is the Kivy-correct architecture for handling animations on a Canvas? I looked at Animation but that seems for more matrix-like animations such as translation, scaling, rotation.
Here is what I am doing now:
I have game with large map window and then a bunch of game UX in helper windows
The game UX helper windows I do all the kivy layouts and such and use generally Images and so my icons are animating nicely
However in the game map, I am using canvas:
Drawing all of my game objects using this paradigm:
r=Rectangle(texture=some_Image.texture)
map.canvas.add(r)
When the world needs to be re-drawn:
1) map.canvas.clear()
2) draw all of the stuff in their new positions and states
(to be faster, I should just track the dirty objects and locations and just draw those, but to be honest I am getting fantastic fps even with this nuclear-level clear on each draw)
This is of course a lot faster and lighter weight than creating and destroying hundreds of widget-classes - what map canvas is for - right?
But the problem is that my icons with animations in a zip file are not animating
Q: Am I thinking of canvas wrong? Should I instead be adding an Image for each of my game objects instead? (And take advantage of all the animated image support?)
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.image import Image
from kivy.app import App
from kivy.graphics import Rectangle
class MainApp(App):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.root = RelativeLayout()
# use any zip file of an animated image
self.animated_icon = Image(source='factory_icon.zip')
# If I add an Image, the icon animates
self.root.add_widget(self.animated_icon)
# If I add the Image's texture on to a Rectangle instruction, no animation
r = Rectangle(texture=self.animated_icon.texture, size=(100, 100), pos=(100, 100))
self.root.canvas.add(r)
def build(self):
return self.root
if __name__ == '__main__':
MainApp().run()
Image.texture property changes in time. It schedules internally methods to update it as the animation goes. This change doesn't propagate to your rectangle because you created it with texture value captured at a very certain point in time, between updates. Consider this example (I use a .gif file for the animation, but the principle should be the same):
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.image import Image
from kivy.app import App
from kivy.graphics import Rectangle
class MainApp(App):
def __init__(self, **kwargs):
super(MainApp, self).__init__(**kwargs)
self.root = RelativeLayout()
animated_icon = Image(source='test.gif')
animated_icon.bind(texture=self.update_texture)
self.r = Rectangle(texture=animated_icon.texture, size=(500, 255), pos=(100, 100))
self.root.canvas.add(self.r)
def update_texture(self, instance, value):
self.r.texture = value
def build(self):
return self.root
if __name__ == '__main__':
MainApp().run()
Here I bind my own update_texture method to image's texture property so every time it changes I can update the rectangle accordingly.
I have some image data that I'm blitting to a texture and displaying in Kivy. The image data is the same width, with a greater height than the widget. After the texture is created, I want to animate the y position of it for a scrolling effect.
Previously, I had been blitting the entire buffer to the texture, and animating the position of the widget itself. However, the buffer data is occasionally larger than the maximum supported texture size of my GPU, and nothing displays on the screen.
I figured a better approach would be to only blit the section of the source buffer I need, and leave the texture the same size as the widget.
After looking at the documentation for Kivy, I discovered there's a pos parameter. I figured I could use an animated property for the document ypos in the call to blit_buffer, but when I tried it, nothing displayed again.
I switched the pos parameter to (0, 100) to see if my usage of the parameter did what I expected, and it still didn't display. Switching it to (0, 0) works as expected.
Am I using blit_buffer() correctly? What's the best way to blit only a portion of an image data buffer?
EDIT: I recreated this issue with a standalone script:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.core.image import Image
from kivy.graphics.texture import Texture
from kivy.graphics import Rectangle
import array
class MyWidget(Widget):
def __init__(self, **kwargs):
super(MyWidget, self).__init__(**kwargs)
texture = Texture.create(size=(512, 512))
buf = [int(x * 255 / (512*512*3)) for x in range(512*512*3)]
buf = array.array('B', buf).tostring()
texture.blit_buffer(
buf,
colorfmt='rgb',
bufferfmt='ubyte',
pos=(0, 100)
)
with self.canvas:
Rectangle(texture=texture, pos=self.pos, size=(512, 512))
class MyApp(App):
def build(self):
return MyWidget()
if __name__ == '__main__':
MyApp().run()
It seems the issue was that the pos argument of Texture.blit_buffer() specifies the destination coordinates of the image data, rather than the source coordinates.
I ended up solving my problem by using PIL to crop the image data, then creating a tiling renderer that contains multiple textures each tile is blit to.
I have one generic icon image, which has an alpha. Lets say a black sphere placed on an square button, with transparancy.
Now I would like to change the color of the icon on the fly, without having several image of sphere_black.png, sphere_red.png etc etc.
Is there a way to colorize the pixmap, respecting the alpha and change HSV on that pixel, for all in the map?
I have something like this, but stuck:
img = QtGui.QImage(kwargs['icon_path']
pxmap = QtGui.QPixmap(img)
for x in range(img.width()):
for y in range(img.height()):
print img.pixel(1, 1), '###'
# ???? #
Any help is appreciated!
QGraphicsColorizeEffect might be what you are looking for. Sadly the QGraphicsEffect class is made to be used with the graphics view framework, it can't easily be applied to a QImage. However there are workarounds for that, as this discussion shows.
The implementation of the effect in QPixmapColorizeFilter::draw() shows how the colourization is done: A coloured rect (with the color having the alpha set to something else than fully opaque) is drawn over the image with QPainter::fillRect(), with an appropriate composition mode set.
I am messing around with Kivy, and trying to get some game like application with some kind of 2D grid. In my design each cell oh grid should have its own graphical representation, depending on whats inside.
Below is my current simple code which just creates grid and insert some images in each cell.
class MyWidget(Widget):
def __init__(self,images,*args, **kwargs):
super(MyWidget, self).__init__(*args, **kwargs)
self.images = []
for img in images:
self.add_image(img)
self.bind(pos=self.callback,size=self.callback)
def add_image(self,image):
self.images.append(Image(source=image,allow_stretch = True,keep_ratio = False))
self.add_widget(self.images[-1])
def callback(self,instance,value):
for image in instance.images:
image.pos = instance.pos
image.size = instance.size
class StartScreen(Screen):
def __init__(self,**kwargs):
super(StartScreen, self).__init__(**kwargs)
i = 10
self.layout = GridLayout(cols=i)
self.add_widget(self.layout)
for i in range(i*i):
self.layout.add_widget(MyWidget(['./images/grass.png','./images/bug1.png']))
class TestApp(App):
def build(self):
return StartScreen()
The problem is that while all images are the same, thay are loaded into memory again for each cell, from what I can see. It is not efficient, especially when there will be 10000 cells or so.
I have tried to add the same image to each cell, but it turns out that each widget can have only one parent.
I also have tried to initialize new images with texture of one already initialized, but that bringed no improve.
I tried to get texture of loaded image, and then create rectangle with it as a texture. Something like this:
def add_image(self,texture):
with self.canvas:
rect = Rectangle(texture=texture,pos=self.pos, size=self.size)
self.rects.append(rect)
where texture is:
Image(source='./images/grass.png',allow_stretch = True,keep_ratio = False).texture
It have improved memory usage (from 430MB to 160MB for 10000 cells with 200kB images. But still, this is quite much for two images. :)
My question: is there more efficient way of creating 2D grid with a lot of repeating images, in Kivy?
Maybe my approach to the problem is flawed - I have really no expirence in creating games...
The memory issue you see is from your widgets not from mismanagement of textures, Image textures in kivy are cached using kivy's internal cache mechanism so if you try and load a image 100 times in one minute kivy will just re-use the existing texture from cache.
This cache does timeout after a set time though and would be re-loaded from disk after this timeout. Image widget for this reason has a reload method and a nocache property that can be set. You can set the cache manually ::
from kivy.cache import Cache
Cache._categories['kv.image']['limit'] = 0
Cache._categories['kv.texture']['limit'] = 0
For games though one should always try and use Atlas, it's cache doesn't timeout, the whole mechanism is geared towards allowing you better performance and texture management. Other advantages include one single texture to be uploaded to the gpu = huge improvement in upload time, read from disk time is reduced considerably.
Every widget no matter how trivial has overhead and when dealing with 10,000+ iterations of the same Widget. You are bound to get memory usage issues. Widgets aren't suitable for such a usage. You should look at using repeating textures,
manipulating texture coordinates, drawing straight onto the canvas without using widgets. Something like (untested)::
texture = Image('grid.jpg').texture
texture.wrap = 'repeat'
texture.uvsize = (20, 20)
with self.canvas:
Color(1, 1, 1)
Rectangle(pos=(0, 0), size=(2000, 2000), texture=texture)
You might want to look at KivEnt and cymunk as physics engines if you are interested in writing games.
I am new to Python and have been working with the turtle module as a way of learning the language.
Thanks to stackoverflow, I researched and learned how to copy the image into an encapsulated postscript file and it works great. There is one problem, however. The turtle module allows background color which shows on the screen but does not show in the .eps file. All other colors, i.e. pen color and turtle color, make it through but not the background color.
As a matter of interest, I do not believe the import of Tkinter is necessary since I do not believe I am using any of the Tkinter module here. I included it as a part of trying to diagnose the problem. I had also used bgcolor=Orange rather than the s.bgcolor="orange".
No Joy.
I am including a simple code example:
# Python 2.7.3 on a Mac
import turtle
from Tkinter import *
s=turtle.Screen()
s.bgcolor("orange")
bob = turtle.Turtle()
bob.circle(250)
ts=bob.getscreen()
ts.getcanvas().postscript(file = "turtle.eps")
I tried to post the images of the screen and the .eps file but stackoverflow will not allow me to do so as a new user. Some sort of spam prevention. Simple enough to visualize though, screen has background color of orange and the eps file is white.
I would appreciate any ideas.
Postscript was designed for making marks on some medium like paper or film, not raster graphics. As such it doesn't have a background color per se that can be set to given color because that would normally be the color of the paper or unexposed film being used.
In order to simulate this you need to draw a rectangle the size of the canvas and fill it with the color you want as the background. I didn't see anything in the turtle module to query the canvas object returned by getcanvas() and the only alternative I can think of is to read the turtle.cfg file if there is one, or just hardcode the default 300x400 size. You might be able to look at the source and figure out where the dimensions of the current canvas are stored and access them directly.
Update:
I was just playing around in the Python console with the turtle module and discovered that what the canvas getcanvas() returns has a private attribute called _canvas which is a <Tkinter.Canvas instance>. This object has winfo_width() and winfo_height() methods which seem to contain the dimensions of the current turtle graphics window. So I would try drawing a filled rectangle of that size and see if that gives you what you want.
Update 2:
Here's code showing how to do what I suggested. Note: The background must be drawn before any other graphics are because otherwise the solid filled background rectangle created will cover up everything else on the screen.
Also, the added draw_background() function makes an effort to save and later restore the graphics state to what it was. This may not be necessary depending on your exact usage case.
import turtle
def draw_background(a_turtle):
""" Draw a background rectangle. """
ts = a_turtle.getscreen()
canvas = ts.getcanvas()
height = ts.getcanvas()._canvas.winfo_height()
width = ts.getcanvas()._canvas.winfo_width()
turtleheading = a_turtle.heading()
turtlespeed = a_turtle.speed()
penposn = a_turtle.position()
penstate = a_turtle.pen()
a_turtle.penup()
a_turtle.speed(0) # fastest
a_turtle.goto(-width/2-2, -height/2+3)
a_turtle.fillcolor(turtle.Screen().bgcolor())
a_turtle.begin_fill()
a_turtle.setheading(0)
a_turtle.forward(width)
a_turtle.setheading(90)
a_turtle.forward(height)
a_turtle.setheading(180)
a_turtle.forward(width)
a_turtle.setheading(270)
a_turtle.forward(height)
a_turtle.end_fill()
a_turtle.penup()
a_turtle.setposition(*penposn)
a_turtle.pen(penstate)
a_turtle.setheading(turtleheading)
a_turtle.speed(turtlespeed)
s = turtle.Screen()
s.bgcolor("orange")
bob = turtle.Turtle()
draw_background(bob)
ts = bob.getscreen()
canvas = ts.getcanvas()
bob.circle(250)
canvas.postscript(file="turtle.eps")
s.exitonclick() # optional
And here's the actual output produced (rendered onscreen via Photoshop):
I haven't found a way to get the canvas background colour on the generated (Encapsulated) PostScript file (I suspect it isn't possible). You can however fill your circle with a colour, and then use Canvas.postscript(colormode='color') as suggested by #mgilson:
import turtle
bob = turtle.Turtle()
bob.fillcolor('orange')
bob.begin_fill()
bob.circle(250)
bob.begin_fill()
ts = bob.getscreen()
ts.getcanvas().postscript(file='turtle.eps', colormode='color')
Improving #martineau's code after a decade
import turtle as t
Screen=t.Screen()
Canvas=Screen.getcanvas()
Width, Height = Canvas.winfo_width(), Canvas.winfo_height()
HalfWidth, HalfHeight = Width//2, Height//2
Background = t.Turtle()
Background.ht()
Background.speed(0)
def BackgroundColour(Colour:str="white"):
Background.clear() # Prevents accumulation of layers
Background.penup()
Background.goto(-HalfWidth,-HalfHeight)
Background.color(Colour)
Background.begin_fill()
Background.goto(HalfWidth,-HalfHeight)
Background.goto(HalfWidth,HalfHeight)
Background.goto(-HalfWidth,HalfHeight)
Background.goto(-HalfWidth,-HalfHeight)
Background.end_fill()
Background.penup()
Background.home()
BackgroundColour("orange")
Bob=t.Turtle()
Bob.circle(250)
Canvas.postscript(file="turtle.eps")
This depends on what a person is trying to accomplish but generally, having the option to select which turtle to use to draw your background to me is unnecessary and can overcomplicate things so what one can do instead is have one specific turtle (which I named Background) to just update the background when desired.
Plus, rather than directing the turtle object via magnitude and direction with setheading() and forward(), its cleaner (and maybe faster) to simply give the direct coordinates of where the turtle should go.
Also for any newcomers: Keeping all of the constants like Canvas, Width, and Height outside the BackgroundColour() function speeds up your code since your computer doesn't have to recalculate or refetch any values every time the function is called.