IO game with paper.js

168 views
Skip to first unread message

Thomas Leitner

unread,
Mar 9, 2021, 6:09:14 AM3/9/21
to Paper.js

Hi there,
if someone is interested in a less intended usecase of paper.js, I am currently working on an IO game, with asp.net Blazor UI/HUD and paper.js game arena rendering.

For someone who is used to cascaded design patterns for UI and games: With paper.js you have no nesting, but a root with child containers (layers) for your elements.

I'm not using paperscript. Gradients, paths, PointText, that get calculated each frame, are positioned absolute without any groupings or whatsoever.
I block paperjs canvas event handling, to be able to lock the mouse pointer.
canvas.addEventListener = function(){};
paper.setup(canvas);


Optimizations include item pooling, paperjs symbols, rasterizing of tweened shapes, and networking moved into webworker, to give paperjs some cpu time on mobile devices.
So feel free to check out the performance yourself: 

https://blobpit.io 

Anyways, I am looking forward to somehow use OffscreenCanvas or to shove >everything< into a WebWorker! :-)
Feel free to ask.
Marooney 

Hai Mac

unread,
Mar 17, 2021, 3:46:33 AM3/17/21
to pap...@googlegroups.com
That's pretty cool, thanks for sharing! I would be interested in learning more about item pooling and rasterisation. What kind of performance improvements did you see? Was it a hassle to swap between the original and rasterised versions of an item?

--
You received this message because you are subscribed to the Google Groups "Paper.js" group.
To unsubscribe from this group and stop receiving emails from it, send an email to paperjs+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/paperjs/39840bf7-a277-4d00-be87-1e2921e2bb42n%40googlegroups.com.
--
Hai

Thomas Leitner

unread,
Mar 17, 2021, 2:43:43 PM3/17/21
to pap...@googlegroups.com
Hey, thanks, I am happy to help!

Object pooling is a design pattern to reduce memory footprint and performance costly object allocations and garbage collection.
So you basically reuse already allocated objects to display different data.
Which of course only makes sense when just a small part of your data gets displayed and the "on the fly" data binding is less costly.
I found a similar explanation here: https://sourcemaking.com/design_patterns/object_pool

A common use case are list items in mobile apps.
You don't allocate and render UI items for >all< data entries (which could be thousands), but just, for the >visible< entries + one extra for the following reason.
The item that scrolls out of view gets moved to the other side of the list, where it gets filled with new data and then scrolls into view.
No new UI element allocations required, just data binding, while the list items get cycled between top and bottom of the list component.

I did the same for the moving blobs. Which are objects, which instantiate multiple paper.js Items (circle path, PointText, HeartSymbol). The paper.js Items are the reason for pooling.
In my case pooling is much easier because most of the data gets constantly updated anyways. So it's just id, name and color that needs to be initialized when taking objects from the pool.
The pool is really just a simple list where you put your obsolete objects in for recycling.

In my case
0. preallocate an useful amount of objects (async while in game menu)
1. iterate over data from server
1.1. for each out of view object: Set invisible, remove from layers, add to pool.
1.2. for each new visible object: Take free object from the pool - else CREATE.
1.2.1. Apply the data to the object.
1.2.2. Add to layer and set visible.
2. (And if required you can also clean up obsolete items in the pool, if they spontaneously pile up on rare events.)

Well, regarding performance, in my case there is not much of a difference if having 100 objects allocated, or just 10.
I just somehow had the situation that "item.visible = false" did not reduce the render time of paper.js. After the pool solution I realized that I probably just needed to remove them from the Layer.
And being able to support thousands of blobs, without stressing the clients too much, is just a nice touch.
But the actual performance gain starts when someone wants to stress your game by constantly connecting/disconnecting which causes all clients to allocate and garbage collect >>player specific<< objects over and over again.

---

paper.js rasterization ( http://paperjs.org/reference/path/#rasterize ):
I positioned, scaled and faded >>path<< items (Heart shapes with arcs and lines) over multiples frames, and even multiple in parallel, with a customer tweener (not with the paperjs tweener).
Dynamic paths are just more expensive than static images. (Performance gain depends on your paths.)  
In my case these animations are not that important and just add up to computation, especially on mobile.
So I just change these paths items into raster items once (function below) and tween them when needed.
Which was actually just a nice refactoring, because like the paper.Path the paper.Raster is a paper.Item as well.  

(Class syntax and google closure annotations)
    /**
     * PUBLIC - Scale and rasterize an item, which then gets removed.
     * @public
     * @param {Path} item paperjs path
     * @param {number} scale item before rasterizing, to avoid pixelation
     * @returns {Raster}
     */
    static rasterize(item, scale) {
        item.scale(scale);
        /** @type {Raster} */
        let raster = item.rasterize();
        item.remove();
        return raster;
    }

Hope that helps :-)
Marooney



--

-------------------------------
Thomas Leitner
- 0664/1052301 -

Reply all
Reply to author
Forward
0 new messages