Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Basic Embedding
#1
I was playing around with Tilengine a little bit, but noticed that the input options were a touch on the limited side. At first, I thought this meant I wouldn't be able to use Tilengine for any of my projects. But then I noticed the embedding that was mentioned on the home page, and I started thinking.

After about an hour worth of experimenting, I was able to fire up a PyGame application, create a Tilengine instance, and get the Tilengine instance correctly rendering its output to a standard PyGame Surface. Now I can have access to all of the PyGame features that I'm used to working with, as well as the tile-map rendering that Tilengine provides. That put a big smile on my face.

I also started glancing over the C# binding for Tilengine, and giving my copy of Unity 3D some very meaningful looks. We'll see how far I can push this embedding thing, and how useful it can be in other contexts. I'll also see about maybe posting an example of the Python project that I started off with. It took a little poking, but it wasn't nearly as complicated as I thought it might be.
Reply
#2
That's very cool man, looking forward to hearing about any progress Smile
Reply
#3
I mentioned posting an example of what I came up with, and I do like to deliver. For any Python-heads out there, here you go...

Code:
import tilengine as tln
import pygame as pgm

#Time to handle the basic start-up code for the Tilengine
tEngine = tln.Engine.create(640, 480, 1, 0, 1)
tBack = tln.Bitmap.fromfile("beach.png") #I'm taking the beach image from the color-cycling example here, you can replace this with any other graphic image you have on hand.
tEngine.set_background_bitmap(tBack) #We're assuming your image is in the same folder, check the standard example projects if you require more advanced resource loading.

#We've got the basics of the Tilengine running, now it's time to fire up our basic PyGame
pgm.init()
pgm.display.set_caption("Tilengine Embed Trial")
pyScreen = pgm.display.set_mode((640, 480), pgm.RESIZABLE) #This fires up PyGame's basic rendering, and returns to us a PyGame Surface to work with. We're using the Resizable flag to test window resizing.

pyTargetRender = pgm.Surface((640, 480), 0, 32) #We have a reference to the screen, but now we want a placeholder Surface that we can use as a target for the Tilengine rendering
pyTargetRender.fill((98, 98, 98, 255)) #We'll fill the target area with a darker grey tone, just so it will stand out from the default background.

tEngine.set_render_target(pyTargetRender._pixels_address, 2560) #This is where the magic happens. We are taking our Tilengine object, and pointing its rendering to the placeholder Surface we created
#We're using the built-in _pixel_address reference in PyGame to retrieve a memory-address for the pixel-buffer of the placeholder Surface. That's what Tilengine needs to focus its rendering on the Surface.
#The number we're providing is for the scanline renderer. We get this number by taking the bit-depth of the Surface, dividing it by 8, and then multiplying it by the width of the target Surface.
#In this case we are getting 4 x 640, which is why we are using 2560. The bit-depth of our Surface is 32, which divided by 8 gives us 4. Keeping your target render width static will help.
#I'm planning on doing any scaling in PyGame, and not worrying about changing the Tilengine render resolution, so it should be fine.

gameRunning = True
while gameRunning:
   for pEvent in pgm.event.get():
       if pEvent.type == pgm.QUIT:
           gameRunning = False
   
   pyScreen.fill((0, 0, 0, 255)) #We'll fill the screen with standard black, which is a good start for frame refreshing
   tEngine.update_frame() #This is the standard function we use to update the Tilengine rendering, it will handle updating the current frame. Any frame-based game logic changes should happen before this.
   pyScreen.blit(pyTargetRender, (0, 0)) #And this is where we draw our placeholder framebuffer to the PyGame screen. Easy enough.
   pgm.display.flip() #A standard function to insure screen refreshing. This manually forces the display to update based on our defined changes.

#That pretty much covers it. A very basic application where we essentially embed Tilengine into a PyGame project. This replaces the stanard windowing within Tilengine with the PyGame windowing system.
#From this point forward, the majority of rendering will be handled by Tilengine. But we will still have access to standard PyGame rendering, as well as all of the other PyGame libraries.
#This includes PyGame input, as well as PyGame audio. You still get the rendering speed and tile-map support of Tilengine, but now you can also leverage the additional features of PyGame.

Please note, I haven't tested this out on a Raspberry Pi yet, only on a Windows machine. It worked just fine on that Windows machine, just note that I haven't given this a thorough wash on multiple platforms. I can't guarantee that it will work everywhere. That said, it works like a charm on Windows.
Reply
#4
Great work Richard! I'm glad you're playing a bit with tilengine, I hope that it's an interesting tool for your projects, and don't be afraid of asking for support if you get stuck somewhere. Documentation is not as developed as it should be.

It would be great to find an online platform to publish tutorials like yours about tilengine. Here inside the forum they won't get much attention, and that would be a shame because this is a very interesting one!
Reply
#5
Thanks, megamarc. I've got a few websites of my own. Some of this will probably find its way to those sites in the future, as more formal tutorials with some graphics and better writing. But time is limited, and I've got a lot on my plate, so I'm not really in any rush.

To be honest, one of the first things I thought of when poking around at Tilengine is that it would be a really great basis for developing some kick-ass point-and-click adventure games. I saw some of the color-cycling demos, and was struck by how low-res backgrounds could look so beautiful with the right palette animations. (and some phenomenal painting) When I started digging deeper into the code, I noticed that the input mapping for mouse support was limited in the bare-bones Tilengine windowing implementation. That's when I thought of wrapping Tilengine inside another engine, such as PyGame. I had already been playing around with PyGame, and had managed to cook up a custom scaling system for low-res games, as well as some basic mouse-management. With embedding of this style, I could easily use all of that work in conjunction with Tilengine.
Reply
#6
Hi Richard,

I've tried your sample that embed tilengine inside pygame, and it works. Cool!
At first I intended that tilengine would be just a rendering library, without any kind of windowing, intended to be embedded inside a bigger framework. But then I found that it would be too demanding for people starting to evaluate tilengine if I requested them to use (or build) their own framework. That would have put off many people. So I developed the built-in windowing as a way to ease initial evaluation and setup. I had in mind console/arcade games, the ones that used tiled graphics hardware: a virtual gamepad or arcade controls, CRT filters... Then some people found that te input layout was too limited, so I added the posibility to acces directly the underlying SDL2 input features. You could use any input method provided by SDL2 directly from the built-in windowing, but there isn't any sample that shows how to do this under the python wrapper, there is just a C sample.

I hope that you can continue developing your integration of pygame and tilengine!
Reply
#7
Took a little break from Python, and switched over to something else I had been curious about. I wanted to know if Tilengine could be profitably integrated into Unity, as an alternative rendering engine to Unity's standard implementation. I'm not really interested in replacing Unity's default rendering, so much as using Tilengine to supplement it with a more authentic version of 2D rendering.

Initial experiments seem good. I was able to feed the Tilengine.dll and SDL2.dll into a standard Unity project as native plugins. I was also able to properly import and reference the Tilengine.cs bindings. Then I cooked up a quick-and-dirty script to reference and initialize the Tilengine Engine object. Worked like a charm, and even ran in the editor preview window with no errors. Of course, that's just the most basic set-up. The real challenge is going to be binding the Tilengine reference to a pixel array. That isn't going to be quite as easy in Unity as it was in PyGame. (where one of the fundamental structures had exactly what I needed) My plan at the moment is to create a RenderTexture in the Unity editor, add a link to it in my script, and use that as a target for both initializing Tilengine, as well as binding it to the pixels for the RenderTexture.

I was a little disappointed to see that the Engine initializer is a singleton instance. Since you can't construct multiple instances of the Engine, it will most likely be impossible to have multiple Engines running within Unity. One of the niftier applications of this experiment would be to have populated RenderTextures that could be used as the texture for a television screen in a 3D game. The 2D focus and low-level performance of Tilengine would probably make for a tighter version than trying to emulate such an effect using Unity's standard implementation of faux-2D 3D sprites and a separate camera. It can still be done, certainly. But you can likely only do it one screen at a time, and wouldn't be able to render multiple different screens in the same scene. A minor limitation that won't effect most people, but still something to think about.
Reply
#8
I Richard!

I'm glad to see you're still playing with it.

Since you started evaluating it, one new feature I added is the use of multiple engine contexts: it is not a singleton anymore. It uses a global "set current context" mechanism (just like OpenGL). For backwards compatibility, this mechanism is transparent if you just creare one instance: the first created instance is selected as the current context by default. But you can create more instances and select between them:
http://www.tilengine.org/doc/_tilengine_...ff340e2e08

Code:
TLN_Engine instance1 = TLN_Init(480, 240, 2,8, 0);    /* instance 1 */
TLN_Engine instance2 = TLN_Init(360, 160, 1,8, 0);    /* instance 2 */

/* do stuff on instance 1: */
TLN_SetContext(instance1);
/* ... do your stuff with regular TLN_ functions */

/* do stuff on instance 2: */
TLN_SetContext(instance2);
/* ... do your stuff with regular TLN_ functions */

/* release */
TLN_DeleteContext(instance1);
TLN_DeleteContext(instance2);

What is not instantiable (yet) is the built-in window, but this is not a problem in you case because you're using your own renderer. The bindings aren't yet updated either. I've separated the bindings to have their own projects/repositories on GitHub. I recommend you to read the new in the facebook page (my official channel news) and upgrade to the latest version.


Let me know about your test in Unity!
Reply
#9
Boom, got it working!

Code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Tilengine;

public class TileHandler : MonoBehaviour {
    public MeshRenderer trialRenderer;

    private Texture2D trialTexture;

    public static Engine trialEngine;

    private byte[] trialBuffer;

    [Range(1, 1920)]
    public int trialWidth;

    [Range(1, 1920)]
    public int trialHeight;

    [Range(1, 256)]
    public int trialLayersNumber;

    [Range(0, 256)]
    public int trialSpritesNumber;

    [Range(0, 256)]
    public int trialAnimationsNumber;

    // Use this for initialization
    void Start () {
        if (trialWidth <= 0) { trialWidth = 1; }
        if (trialHeight <= 0) { trialHeight = 1; }
        if (trialLayersNumber <= 0) { trialLayersNumber = 1; }

        trialEngine = Engine.Init(trialWidth, trialHeight, trialLayersNumber, trialSpritesNumber, trialAnimationsNumber);
        trialEngine.LoadPath = "Assets/TrialMap";

        trialTexture = new Texture2D(trialWidth, trialHeight, TextureFormat.BGRA32, false, false);
        trialTexture.anisoLevel = 0;
        trialTexture.filterMode = FilterMode.Point;

        trialBuffer = trialTexture.GetRawTextureData();

        trialEngine.SetRenderTarget(trialBuffer, 4 * trialWidth);
        Tilengine.Color trialBack = new Tilengine.Color(0x1B, 0x00, 0x8B);
        trialEngine.SetBackgroundColor(trialBack);

        if (trialRenderer != null) {
            trialRenderer.material.mainTexture = trialTexture;
        }
    }
    
    // Update is called once per frame
    void Update () {
        trialEngine.BeginFrame(Time.frameCount);
        for (int i = 0; i < trialHeight; i++) {
            trialEngine.DrawNextScanline();
        }
        trialTexture.LoadRawTextureData(trialBuffer);
        trialTexture.Apply();
    }
}

It took a little experimenting, but it wasn't nearly as difficult as I feared. This generates a dynamically-created Texture2D object, and then uses a byte array along with Tilengine to render content to that Texture2D object. Texture2D is a standard Unity class for dynamically generating and editing pixel textures on-the-fly, and can be applied to pretty much any standard Unity shader or material that accepts texture input. (most of them) There are several different ways to use this to display the rendered content. The method I used in the example is to link to an objects mesh rendering context, and assign the Texture2D as its mainTexture reference.
Reply
#10
Congratulations! That is the idea, to use a streaming texture (optimized for frequent writes) as the target surface, and the use that texture inside the regular rendering pipeline of the framework.

I recommend you a change inside the code: use the Engine method UpdateFrame() instead of the BeginFrame() / DrawNextScanline() pair:

 
Code:
void Update () {
     trialEngine.UpdateFrame(Time.frameCount);
     trialTexture.LoadRawTextureData(trialBuffer);
     trialTexture.Apply();
 }

I exported those methods for situations where the use of callbacks (delegates in C# terminology) is not possible on the binded language, so one must do active calls of the rendering scanlines AND the raster effect function. That's not the case in C#.

Take a look at this link. It explains the original C API, but you got the idea:
http://www.tilengine.org/doc/page_render.html
Reply


Forum Jump:


Users browsing this thread: 9 Guest(s)