Catégorie : unity

Serveur asynchrone TCP Python. Et le client C# Unity !

Deux exemples très courts pour vous mettre sur les rails, qui envoient et reçoivent du binaire « pur » = très peu de bande passante, avec une connexion persistante.

Je vous donne deux envois-réception qui devraient vous permettre de faire tous vos envois binaires :

  1. C# : le client envoie un octet, qui correspond à un booléen, pour dire s’il est en big ou little endian ;
  2. C# : le client envoie un message encodé en UTF-8 (oui j’ai trouve la solution qui fonctionne !) ;
  3. Python : le serveur lit ce booléen ;
  4. Python : le serveur lit le message et le dit à voix haute (sous Windows) ;
  5. Python : le serveur envoie un entier non signé, puis deux float ;
  6. C# : le client lit l’entier non signé puis deux floats.

Avec ça, vous avez de quoi comprendre et faire tous les échanges que vous voulez !

Serveur asynchrone TCP Python

import asyncio
import struct
from asyncio import StreamWriter, StreamReader
import pythoncom
import win32com.client as win32_client
HOST = '192.168.1.31'
PORT = 9696
async def handle(reader: StreamReader, writer: StreamWriter):
    is_little_endian = False
    buffer = bytearray(100)
    addr = writer.get_extra_info('peername')
    print(f"Connected with {addr!r}")
    is_little_endian, = struct.unpack_from(
        '?', await reader.read(struct.calcsize('c'))
    )
    print(f'{is_little_endian=}')
    data = await reader.read(4096)
    message = data.decode('utf8')
    pythoncom.CoInitialize()
    speak = win32_client.Dispatch('SAPI.SpVoice')
    speak.Speak(message)
    print(f"Received {message!r} from {addr!r}")
    print(f"Send: {message!r}")
    float1 = 1.1
    float2 = 2.2
    struct.pack_into(
        # =: native order, std. size & alignment
        # H: unsigned short
        # f: float
        "=Hff",
        buffer, 0, 1, float1, float2)
    writer.write(buffer)
    await writer.drain()
    print("Close the connection")
    writer.close()
async def main():
    server = await asyncio.start_server(handle, HOST, PORT)
    print(f'Serving on {server.sockets[0].getsockname()}')
    async with server:
        await server.serve_forever()
asyncio.run(main())

Client C# Unity

using System;
using System.IO;
using System.Net.Sockets;
using UnityEngine;
public class Connexion : MonoBehaviour
{
    public string server;
    public string message;
    public ushort port;
    private void Start()
    {
        // working sample to send text:
        byte[] data = System.Text.Encoding.UTF8.GetBytes(message);
        byte isLittleEndian = BitConverter.IsLittleEndian ? (byte)1 : (byte)0;
        TcpClient client = new TcpClient(server, port);
        NetworkStream stream = client.GetStream();
        // Send the message to the connected TcpServer.
        stream.WriteByte(isLittleEndian);
        stream.Write(data, 0, data.Length);
        Debug.Log($"Sent: {message}");
        // read sample
        BinaryReader reader = new BinaryReader(stream);
        uint len = reader.ReadUInt16();
        var x = reader.ReadSingle();
        var y = reader.ReadSingle();
        Debug.Log("len=" + len);
        Debug.Log($"x={x}, y={y}");
    }
}

Pour la note, ces deux exemples paraissent simples, mais ils m’ont pris un temps fou, et je n’ai eu aucune réponse au bout de 3 semaines sur stackoverflow…

Unity

Unity aide-mémoire

GameObjects : mémo

Taille Mesh Mesh m = obj.GetComponent<MeshFilter>().sharedMesh;
Debug.Log(m.bounds.size);
Taille Sprites var m = transform.GetComponent<Renderer>();
Debug.Log(m.bounds.size);

Assets et Objets

Pour comprendre comment gérer correctement les données dans Unity, il faut impérativement comprendre comment Unity identifie et sérialise les données.
Le premier point-clé est de faire la distinction entre les Assets et UnityEngine.Objects.

Un Asset est un fichier sur le disque, stocké dans le dossier Assets d’un projet Unity. Les textures, modèles 3D, ou encore clips audio sont des types d’Assets très courants. Quelques Assets contiennent des données au format natif de Unity, tels que les materiaux. D’autres Assets doivent être convertis en format natif, comme par exemple les fichiers FBX.

UnityEngine.Object, ou bien Object avec un ‘O‘majuscule, est un ensemble de données sérialisées qui décrivent une instance spécifique d’une ressource. Cela peut être n’importe quelle ressource utilisée par le Unity Engine, telle qu’un mesh, sprite, AudioClip ou AnimationClip. Tous les objets sont des sous-classes de la classe de base UnityEngine.Object.

Alors que la plupart des types Object sont built-in (= natifs), il y a deux types spéciaux.

  1. Un ScriptableObject fournit un système pratique pour les développeurs qui désirent définir leurs propre types de données. Ces types peuvent être nativement sérialisés et dé-sérialisés par Unity, et manipulés dans la fenêtre « inspecteur » de l’éditeur de Unity
  2. Un MonoBehaviour fournit un wrapper qui est lié à un MonoScript. Un MonoScript est un type de données interne que Unity utilise pour garder une référence à une class spécifique de script à l’intérieur d’un namespace et assembly spécifique. Un MonoScript ne contient pas réellement de code exécutable.

Il y a une relation one-to-many entre Asset et Object. Dit autrement, un fichier Asset peut contenir un ou plusieurs Object.

– Créer l’URL de logout :
url(r'^logout/$', LogoutView.as_view(), name='logout'),

– Créer la vue LogoutView
from django.contrib.auth import views

class LogoutView(views.LogoutView):

    def __init__(self):
        self.next_page = '/'

<form action="{% url 'login' %}" method="post" accept-charset="utf-8">
    {% csrf_token %}
    {% for field in form %}
        <label>{{ field.label }}</label>
        {% if field.errors %}
            {{ field.errors }}
        {% endif %}
        {{ field }}
    {% endfor %}
    <input type="hidden" name="next" value="{{ next }}" />
    <input class="button small" type="submit" value="Submit"/>
</form>

Références entre Object‘s

Tous les UnityEngine.Objects peuvent avoir des références vers d’autres UnityEngine.Objects. Ces derniers peuvent résider dans le même fichier Asset, ou peuvent être importé à partir d’autres fichiers Asset. Par exemple, un Object matériau a habituellement une ou plusieurs références vers des Object texture. Ces Object texture sont généralement importés à partir d’un ou plusieurs autres fichiers Asset (tels que des PNG‘s ou JPG‘s).
Lorsqu’elles sont sérialisées, ces références sont constituées en deux blocs de données : un File GUID et un Local ID.
Le File GUID identifie le fichier Asset. Un Local ID identifie chaque objet à l’intérieur d’un fichier Asset, car un fichier Asset peut contenir plusieurs objets.
L’identification et le système de référence peut être visualisé dans un éditeur de texte : créez un nouveau projet Unity et changez Editor Settings en cochant Visible Meta Files et Serialiaze Assets as text. Créez un nouveau matérieau, puis importez une texture dans le projet. Assignez le matériau à un cube dans la scène et sauvez cette dernière.
Avec un éditeur de texte, ouvrez le fichier .meta associé au matériau. Une ligne guid sera présente parmi les première lignes. Cette ligne définit le GUID du matériau. Pour trouver l’id local, ouvrez le fichier matériau dans un éditeur de texte. La première ligne qui ressemble à ‘--- !u! &2100000‘ correspond à l’id local.

Pourquoi des « File GUIDs » et des Local ID‘s ?

Pourquoi le principe des « File GUIDs » et des Local ID‘s est-il important  ? La réponse est : dans la stabilité et la possibilité d’avoir un workflow indépendant de la plateforme.

Comment faire un affichage 100% proportionnel quelle que soit la résolution de l’écran ?

En fait, d’après ce que j’ai compris, il faut comprendre qu’on ne « devrait » pas toucher à la taille de la fenêtre de l’application : c’est l’utilisateur qui la fixe : téléphone mobile, portrait ou paysage, ou écran 4k voire 8k, l’application doit s’adapter et devrait même être redimensionnable.
L’idée que j’ai retirée de mon expérience, qui est peut-être un peu différente de la réalité « Unity », est la suivante :

  • Il faut créer un Canvas et lui dire de rester proportionnel à l’écran. Comme cela, même sur les écrans géants, il s’adaptera ;
  • Dans ce Canvas, il faut créer un « référent » qui servira de conteneur à tout le reste. Ce conteneur, il faut lui dire de « remplir » son parent (donc de remplir le Canvas) selon une échelle à

Pourquoi ne pas faire ces deux opérations dans le Canvas ? (1) dire de rester proportionnel + (2) dire de « remplir » le parent Parce que le Canvas connaît et gère le composant Canvas Scaler, et le « référent » (qui sera un Pourquoi ne pas faire ces deux opérations dans le Canvas ? Parce que le Canvas connaît et gère le composant Canvas Scaler et que le « référent », ou « conteneur », connaît et gère le composant Aspect Ratio Fitter)