EloiStree / HelloRC

You want to learn the basic of electricity or the code. Why not while having fun with RC cars :) ?
0 stars 0 forks source link

Step-by-Step: Scratch To Car RC in XR with HeRC (FR) #43

Open EloiStree opened 9 months ago

EloiStree commented 9 months ago

To do today

image

image image image

image

image

Mon but permettre de s'amuser ensemble sur un jeu/ des jeux en groupe projeté sur l'écran durant l'atelier sur Twitch après l'atelier.

Pour rester sérieux ;) sur ce que l'on peut faire en une semaine.

Ça prend 6 mois pour apprendre à maîtriser Unity.
Deux à cinq ans pour travailler dans le domaine.
Une décennie pour se sentir professionnel.
Une vie entière pour vivre du métier.

À l'inverse, le code et sa logique s'apprennent très vite.
Encore plus avec le support de Chat GPT.
Rien ne peut vous arrêter d'être créatif :)

Qu'allons-nous faire cette semaine ?

Apprendre à jouer ensemble dans un jeu à plusieurs sur le même écran :) Vous permettre de créer votre propre jeu sous Unity3D et Scratch

L'atelier continue pour ceux qui le désirent après la semaine sur Twitch.

Finalité de l'atelier est de jouer ensemble avec vos créations sur le même écran.
Voir en réalité virtuelle.

Allons-y allons-z-en :) !


EloiStree commented 9 months ago

Avant de commencer quelque liens

Scratch

Créé une voiture sur scratch

Voir:

22 C'est quoi scratch

11 Plein de projet de bon ne qualité en scratch

15 Des bonnes vidéos trouvées sur le chemin en apprenant.

Example utilisant une voiture: https://scratch.mit.edu/projects/965584985/editor

Example utilisant un drone https://scratch.mit.edu/projects/966123505/editor

Créer un IntCmd sur Scratch pour le project "Hello Car Drone RC"

L'idée ici est de généré un entier (int) qui server de command pour jouer.
La taille maximum d'un entier est 2,147,483,647
ET donc
1 999 999 999 ce que je traduis pour l'exercice en
1 0000 9999 1

1 Les numéros qui suive sont des commands de voiture et de drone. 0000 Voici le valeur de la manette de la voiture. 9999 Voici le valeur de la manette du drone. 1 L'utilisateur demande une action à faire

Voir:

17 Plus de détails sur 1 000 9999 1

3 C'est quoi un entier

Voici des examples de comment gérer ces valeurs sur un Cloud Var de Scratch.

En utilisant des sliders image

En utilisant des couleurs image

En utilisant un clavier image

En utilisant un view port image

Mon premier brouillons du concept: image https://scratch.mit.edu/projects/960534356

C'est quoi du Python

Le python est un language qui connu par 1/3 des développeurs dans le monde. Il est assez simple et permet de parler avec l'ordinateur (coder) très rapidement.

Example: Spammer un text avec enter à l'aide du clipboard de window.
Code généré par Chat GPT: https://chat.openai.com/share/f4991395-f8e1-435d-a892-96a7522ccd33

import time
import pyperclip
import ctypes

def press_ctrl_v():
    # Simuler l'appui sur la touche "Entrée"
    ctypes.windll.user32.keybd_event(0x0D, 0, 0, 0)  # Touche pressée
    ctypes.windll.user32.keybd_event(0x0D, 0, 0x0002, 0)  # Touche relâchée

    # Simuler l'appui sur les touches de raccourci "Ctrl+V"
    ctypes.windll.user32.keybd_event(0x11, 0, 0, 0)  # Appui sur la touche "Ctrl"
    ctypes.windll.user32.keybd_event(0x56, 0, 0, 0)  # Appui sur la touche "V"
    ctypes.windll.user32.keybd_event(0x11, 0, 0x0002, 0)  # Relâchement de la touche "Ctrl"
    ctypes.windll.user32.keybd_event(0x56, 0, 0x0002, 0)  # Relâchement de la touche "V"

    # Simuler l'appui sur la touche "Entrée"
    ctypes.windll.user32.keybd_event(0x0D, 0, 0, 0)  # Touche pressée
    ctypes.windll.user32.keybd_event(0x0D, 0, 0x0002, 0)  # Touche relâchée

def main():
    start_time = time.time()
    end_time = start_time + 30  # Exécuter pendant 30 secondes

    while time.time() < end_time:
        # Écrire "Bonjour Python" dans le presse-papiers
        pyperclip.copy("Bonjour Python")

        # Simuler le collage
        press_ctrl_v()

        # Attendre 3 secondes
        time.sleep(3)

if __name__ == "__main__":
    main()

Pour faire fonctionner ce text que l'on appelle du code vous devez avoir Python d'installer sur votre ordinateur.

Comment on install Python image image

Comme l'on utiliser un import pyperlib qui vient du web. L'on doit l'installer sur notre ordinateur pip install pyperclip
image ( Window + R > cmd > Enter >write pip install pyperclip )

C'est quoi un cloud var ?

C'est juste une petit variable double ( #4 ) Qui est stocker sur un server de Scratch. L'équpie de Scratch nous laisse accès à 10 variables par projet.
Pour les utiliser et en créé, vous devez être un Scratcher #44 .
Quand vous poussez les cloud var dans leur limite, vous pouvez faire du multijoueur #28 .

Lire les clouds var

image

Utiliser de l'UDP, c'est quoi de l'UDP

L'on va utiliser dans notre projet des messages UDP. Pas de panique, l'on va rester simple.

En résumer, vous pouvez envoyé des messages textes "J'aime les frites" à un ordinateur. Celui-ci sur le réseaux à un numéro qui le définit, ce que l'on appel IP (127.0.0.1 , 192.168.1.1 , ...)

127.0.0.1 Veux dire le PC sur lequel je suis.

Pour savoir l'address IP que l'on a. Il suffit d'écrire ipconfig dans les invités de commande (la console): image

Maintenant quel l'on peu envoyé un message à un ordinateur on a besoin de dire à quel application. Comme l'on ne sait pas le nom de celle-ci et par mesure de sécurité. L'on envoi le message sur une numéro et l'ordinateur se débrouillera. Ce que l'on appel un port.

Example, IP puis Port: 192.168.1.254:7073

Example Chat GPT d'envoie et reception UDP

45 Envoyé des messages textes

46 Envoyé deux entiers en tableau de bytes

En python

Envoyé un message

import socket

# Adresse IP et port du récepteur UDP
UDP_IP = "127.0.0.1"
UDP_PORT = 5005

# Création du socket UDP
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# Envoi du message
message = "Hello, world!"
sock.sendto(message.encode(), (UDP_IP, UDP_PORT))

# Fermeture du socket
sock.close()

Recevoir le message

import socket

# Adresse IP et port d'écoute UDP
UDP_IP = "127.0.0.1"
UDP_PORT = 5005

# Création du socket UDP
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# Lier le socket à l'adresse et au port
sock.bind((UDP_IP, UDP_PORT))

print("En attente de messages...")

# Boucle de réception de messages
while True:
    data, addr = sock.recvfrom(1024)  # Taille maximale du message à recevoir est de 1024 octets
    print("Message reçu:", data.decode())

# Fermeture du socket
sock.close()

Avec Python via API

Code: https://github.com/EloiStree/2024_02_05_ScratchLogOverwatch/tree/main/Python

De nombreuse library que j'ai tester n'ont pas fonctioner. ScratchSession à fonctionner et voici un code de comment l'utiliser pour envoyé des messages UDP.


import socket
from scratchclient import ScratchSession
import threading
import time
import random
import json
import inspect

host='127.0.0.1'
port=7073
# TDD random
## https://scratch.mit.edu/projects/967799973/
project_id=967799973
# Controller with cloud var
## https://scratch.mit.edu/projects/960534356/
project_id=960534356
def send_udp_message(message):
    try:
        # Create a UDP socket
        with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
            # Send the message
            s.sendto(message.encode('utf-8'), (host, port))
        print(f"UDP message sent to {host}:{port}: {message}")
    except Exception as e:
        print(f"Error sending UDP message: {e}")

def read_file(file_path):
    try:
        with open(file_path, 'r') as file:
            file_content = file.read()
            return file_content
    except FileNotFoundError:
        return f"Error: File '{file_path}' not found."
    except Exception as e:
        return f"Error: {e}"

connection=""
def reconnect():
    global connection, session
    #password_path="password.txt"
    password_path="C:\\Users\\elois\\Desktop\\temp\\password.txt"

    print("Read Password.")

    #password= read_file("password.txt")
    password= read_file(password_path)

    name ="EloiStree"

    print("Start connection to scratch")
    session = ScratchSession(name, password)

    connection = session.create_cloud_connection(project_id)
    connection.on("set")

reconnect()

def do_the_stuff():
    # Replace this with the action you want to perform
    try:
        varRandom= random.randint(1, 9999999)
        print(f"Ping {varRandom}")
        connection.set_cloud_variable("ServerPing",varRandom )
    except:
        reconnect()

def action_thread():
    while True:
        do_the_stuff()
        time.sleep(1)

print("Is log test:")
print(session.get_studio(34580905).description)
#print(session.get_project(project_id).get_comments()[0].content)

@connection.on("set")
def on_set(variable):

    try:
        variable.name=variable.name.replace("☁ ", "")
        print(f"CLOUD:{variable.name}:{variable.value}")
        send_udp_message(f"scratchvar:{variable.name}:{variable.value}")
    except:
        reconnect()
        connection.on("set")

print("Start ping Thread")
# Create a thread
thread = threading.Thread(target=action_thread)
# Start the thread
thread.start()
thread.join()

# Add any other code here if you want to do something else concurrently

# Wait for the thread to finish (you can also use thread.join() to wait for it)

#print("T2")
#connection.set_cloud_variable("Test", 42)
#print(connection.get_cloud_variable("Test"))

#print("End")

Avec C# via page web

L'idée ici est de faire du "Scraping", l'on va aller grater les informations que l'on a sur le web de manière semi-officiel.

Manière illégale serait d'utiliser Selenium pour extraire le code HTML de la page. Cette que l'on utiliser ici est légal car l'on utiliser un code prévu pour nous laisser accès au information.

Mais l'utilisation d'un API officiel où l'on est identifiable est la meilleur solution (voir la solution python) dans la théorie

Votre premier Json :)
Accéder aux données à la main.

C'est ce texte que l'on va télécharger et lire dans Unity. Le but de l'atelier n'est pas d'apprendre à lire du JSON. Donc, nous allez passer à la suite et utiliser sans comprendre.

Code: https://github.com/EloiStree/2024_02_05_ScratchLogOverwatch/

image

Example en version Python: image Chat GPT Generated: https://chat.openai.com/share/7a57c980-00bc-4580-96df-00f7dcd1dbde


import json
import requests

def telecharger_page(url):
    try:
        # Envoyer une requête GET à l'URL spécifiée
        reponse = requests.get(url)

        # Vérifier si la requête a réussi (statut 200)
        if reponse.status_code == 200:
            # Retourner le contenu de la page
            return reponse.text
        else:
            print("La requête a échoué avec le statut :", reponse.status_code)
            return None
    except Exception as e:
        print("Une erreur s'est produite lors de la requête :", str(e))
        return None

# URL de la page à télécharger
url = "https://clouddata.scratch.mit.edu/logs?projectid=967408633&limit=100&offset=0"

# Appeler la fonction pour télécharger la page
contenu_page = telecharger_page(url)

# Vérifier si le contenu de la page a été récupéré avec succès
if contenu_page is not None:
    # Charger le JSON à partir du contenu de la page téléchargée
    data = json.loads(contenu_page)

    # Parcourir chaque élément dans le tableau JSON
    for element in data:
        print("Utilisateur:", element["user"])
        print("Verbe:", element["verb"])
        print("Nom:", element["name"])
        print("Valeur:", element["value"])
        print("Timestamp:", element["timestamp"])
        print()  # Ajouter une ligne vide pour séparer chaque élément
else:
    print("Impossible de télécharger le contenu de la page.")

Brouillons


Index IntCmd

Indroduction Text vs binaire.

Ce que je veux permettre de faire, c'est de jouer ensemble à un jeu de voiture virtuelle.

Mais aussi de vous apprendre à jouer avec des vrais voitures dans la vrai vie avec des Arduino et Raspberry Pi ( un autre atelier )

L'on pourrait utiliser de l'UDP comme l'on a vu ensemble avec du texte. Et hors de cette atelier, je vous conseilles d'apprendre à le faire, c'est très fun et permet déjà beaucoup de choses.

Pourquoi le texte nous bloque ? Hormis le fait que du texte laisse de la place à un hacker pour votre jeu.

Les textes 1000099991 dans la mémoire de votre ordinateur en version text vaut 10 bytes 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 Contre 4 en version integer: 11111111 11111111 11111111 11111111

Si vous écriver en texte:

Vous utilisez +-40 bytes contre 4 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 - 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 - 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 - 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 Contre 4 en version integer: 11111111 11111111 11111111 11111111

Vous allez me dire que c'est pas trop grave avec la technologie actuelle on fait des miracles.

C'est vrai. Sauf que si vous utiliser du Bluetooth chez vous après cette atelier, vous êtes limiter par une fréquence d'onde qui n'est pas très rapide.

Chaque 1010101 qui passe prendre un temps à être transmit et reçu.

Dans le cas du jeu Hello Car RC que l'on va jouer vendredi en réalité virtuelle. La raison est tout autre.

Le but est de faire jouer 5-10-200 voir 10000 joueur sur le même jeu :) Chaque 11111111 économisé permet plus de joueur sur le jeu.

Transformer les cloud var d'un projet scratch en deux entiers ?

Si l'on veut un temps de réaction sur entre la manette scratch et notre jeux pour jouer ensemble. La meilleur manière et d'utiliser la librairie python. ( Ou simplement de ne pas utiliser Scratch :)- )

Transformer les clouds Scratch en deux entiers

Some cloud var to read from: image

Avec le code python suivant, nous allez envoyé les clouds var de Scratch avec un petit latence de <0.3 seconds vers le notre jeu via text sur un port A et via IndexIntCmd via un port B.


import socket
from scratchclient import ScratchSession
import threading
import time
import random
import json
import struct
import inspect

# Where is your password to connect at scratch

password_path="C:\\Users\\elois\\Desktop\\temp\\password.txt"
#What is the name of your scratch account
name ="EloiStree"

# What is the computer you want to redirect value too
host='127.0.0.1'
# What is the port (the app) that need to received those information
port=12344
# What is the computer you want to redirect index int cmd too
host_int_cmd='127.0.0.1'
# What is the port (the app) that need to received those int cmd information
port_int_cmd=12346

# What is the project ID of where the cloud var need to be read
# TDD random https://scratch.mit.edu/projects/967799973/
# Controller with cloud var https://scratch.mit.edu/projects/960534356/
# Range of test: https://scratch.mit.edu/projects/967408633
project_id=967799973

cloud_var_label_to_int = {}
cloud_var_label_to_int["☁ int_cloud_input_with_anti_Spam"]=42
cloud_var_label_to_int["☁ CV0"] = 50000
cloud_var_label_to_int["☁ CV1"] = 50001
cloud_var_label_to_int["☁ CV2"] = 50002
cloud_var_label_to_int["☁ CV3"] = 50003
cloud_var_label_to_int["☁ CV4"] = 50004
cloud_var_label_to_int["☁ CV5"] = 50005
cloud_var_label_to_int["☁ CV6"] = 50006
cloud_var_label_to_int["☁ CV7"] = 50007
cloud_var_label_to_int["☁ CV8"] = 50008
cloud_var_label_to_int["☁ CV9"] = 50009

#Example of Get Value
value = cloud_var_label_to_int["☁ int_cloud_input_with_anti_Spam"]
print(value)

def send_udp_message(message):
    try:
        # Create a UDP socket
        with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
            # Send the message
            s.sendto(message.encode('utf-8'), (host, port))
        print(f"UDP message sent to {host}:{port}: {message}")
    except Exception as e:
        print(f"Error sending UDP message: {e}")

def read_file(file_path):
    try:
        with open(file_path, 'r') as file:
            file_content = file.read()
            return file_content
    except FileNotFoundError:
        return f"Error: File '{file_path}' not found."
    except Exception as e:
        return f"Error: {e}"

connection=""
def reconnect():
    global connection, session
    #password_path="password.txt"

    print("Read Password.")

    #password= read_file("password.txt")
    password= read_file(password_path)

    name ="EloiStree"

    print("Start connection to scratch")
    session = ScratchSession(name, password)

    connection = session.create_cloud_connection(project_id)
    connection.on("set")

reconnect()

def do_the_stuff():
    # Replace this with the action you want to perform
    try:
        varRandom= random.randint(1, 9999999)
        print(f"Ping {varRandom}")
        connection.set_cloud_variable("ServerPing",varRandom )
    except:
        reconnect()

def action_thread():
    while True:
        do_the_stuff()
        time.sleep(1)

print("Is log test:")
print(session.get_studio(34580905).description)
#print(session.get_project(project_id).get_comments()[0].content)

def send_intCmd_value(intIndex, intValue):
    # Convert text inputs to integers
    intIndex = int(intIndex)
    intValue = int(intValue)

    # Create a bytes array with intIndex and intValue as integers
    #data = intIndex.to_bytes(4, byteorder='big') + intValue.to_bytes(4, byteorder='big')

    ##WARNING INTEGER are not the same in binary in python or C# Apparently
    ## THIS line convert them
    data = struct.pack('<ii', intIndex, intValue)

    print(f"Sent{host}:{port}: {intIndex} {intValue}")

    # Create UDP socket
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    try:
        # Send data via UDP
        sock.sendto(data, (host_int_cmd, port_int_cmd))
        print("Data sent successfully.")
    except Exception as e:
        print("Error occurred while sending data:", e)
    finally:
        # Close the socket
        sock.close()

@connection.on("set")
def on_set(variable):
    print(f"CLOUD:{variable.name}:{variable.value}")
    send_intCmd_value(cloud_var_label_to_int[variable.name],variable.value)
    try:

        variable.name=variable.name.replace("☁ ", "")

        send_udp_message(f"scratchvar:{variable.name}:{variable.value}")

    except:

        reconnect()
        connection.on("set")
    print(f"Sent Success")

print("Start ping Thread")
# Create a thread
thread = threading.Thread(target=action_thread)
# Start the thread
thread.start()
thread.join()

# Add any other code here if you want to do something else concurrently

# Wait for the thread to finish (you can also use thread.join() to wait for it)

#print("T2")
#connection.set_cloud_variable("Test", 42)
#print(connection.get_cloud_variable("Test"))

#print("End")

Scratch c'est bien mais c'est lent.

L'avantage d'utiliser Scratch c'est que c'est en ligne héberger par scratch facile à manipuler et éditer.

Le désavantage c'est que l'utiliser comme une manette de jeu, c'est lente. entre 0.1-1 seconds de latence. Quand les serveurs de Scratch sont pas arrêter.

Ce qui veut dire que pour l'exercice c'est pas toujours idéal.

Je vous ai donc préparé deux petits code pour la suite de l'exercice.

Code python qui envoie des IndexIntCmd aléatoire

import random
import time
import socket
import struct

def generate_random_number():
    return random.randint(0, 999999999)

# What is the computer you want to redirect index int cmd to
host_int_cmd = '127.0.0.1'
# What is the port (the app) that need to received those int cmd information
port_int_cmd = 12346
user_int_id = generate_random_number()

def send_intCmd_value(intIndex, intValue):
    # Convert text inputs to integers
    intIndex = int(intIndex)
    intValue = int(intValue)

    # Create a bytes array with intIndex and intValue as integers
    data = struct.pack('<ii', intIndex, intValue)

    print(f"Sent {host_int_cmd}:{port_int_cmd}: {intIndex} {intValue}")

    # Create UDP socket
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    try:
        # Send data via UDP
        sock.sendto(data, (host_int_cmd, port_int_cmd))
        print("Data sent successfully.")
    except Exception as e:
        print("Error occurred while sending data:", e)
    finally:
        # Close the socket
        sock.close()

while True:
    random_number = generate_random_number()
    print("Nombre aléatoire:", random_number)
    send_intCmd_value(user_int_id, random_number)
    time.sleep(random.uniform(0.1, 3))

Un keylogger en IndexIntCmd

import keyboard
import threading
import time
from keyboard import KEY_DOWN, KEY_UP
import random
import socket
import struct

def generate_random_number():
    return random.randint(0, 999999999)

intCmdValue =1000000000
intCmdValuePrevious =1000000000

# Random player id
intIndex_player =generate_random_number()
# Fixed player id
#intIndex_player =78645

# What is the computer you want to redirect index int cmd to
host_int_cmd = '127.0.0.1'
# What is the port (the app) that need to received those int cmd information
port_int_cmd = 12346

def send_intCmd_value(intIndex, intValue):
    # Convert text inputs to integers
    intIndex = int(intIndex)
    intValue = int(intValue)

    # Create a bytes array with intIndex and intValue as integers
    data = struct.pack('<ii', intIndex, intValue)

    print(f"Sent {host_int_cmd}:{port_int_cmd}: {intIndex} {intValue}")

    # Create UDP socket
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    try:
        # Send data via UDP
        sock.sendto(data, (host_int_cmd, port_int_cmd))
        print("Data sent successfully.")
    except Exception as e:
        print("Error occurred while sending data:", e)
    finally:
        # Close the socket
        sock.close()

def on_value_changed():
        global intCmdValuePrevious
        value = 1000000000  # Initialize value to a large number
        if key_state.get('a') == 1 or  key_state.get('num7') == 1:  
            value += 100000000  
        if key_state.get('e') == 1 or  key_state.get('num9') == 1:  
            value += 10000000  
        if key_state.get('q') == 1 or  key_state.get('num4') == 1:  
            value += 1000000  
        if key_state.get('d') == 1 or  key_state.get('num6') == 1:  
            value += 100000  
        if key_state.get('h') == 1:  
            value += 90000  
        elif key_state.get('f') == 1:  
            value += 20000  
        if key_state.get('t') == 1:  
            value += 9000  
        elif key_state.get('g') == 1:  
            value += 2000

        if key_state.get('l') == 1:  
            value += 900  
        elif key_state.get('j') == 1:  
            value += 200

        if key_state.get('i') == 1:  
            value += 90  
        elif key_state.get('k') == 1:  
            value += 20

        if key_state.get('space') == 1 or  key_state.get('num8') == 1:  
            value += 1  
        if(value!= intCmdValuePrevious):
            intCmdValuePrevious= value
            send_intCmd_value(intIndex_player, value)
            print(f"Input: {value}")  # Print the value

# Dictionary to store the state of keys
key_state = {}

# Function to handle key press events
def on_press(event):
    if event.name in {'q','a','e', 'd', 's', 'z', 'f', 'h', 't', 'g',
                      'j', 'l', 'i', 'k', 'space',
                      'num1', 'num2', 'num3', 'num4', 'num5', 'num6', 'num7', 'num8', 'num9', 'num0', 'num*','num/','num+', 'num-'}:     
        key_state[event.name] = 1
        print(f"{event.name} Key Pressed")
        on_value_changed()

# Function to handle key release events
def on_release(event):
    if event.name in {'q','a','e', 'd', 's', 'z', 'f', 'h', 't', 'g', 'j', 'l', 'i', 'k', 'space','num1', 'num2', 'num3', 'num4', 'num5', 'num6', 'num7', 'num8', 'num9', 'num0', 'num*','num/','num+', 'num-'}:
        key_state[event.name] = 0
        print(f"{event.name} Key Released")
        on_value_changed()

# Function to handle both key press and release events
def on_action(event):
    if event.event_type == KEY_DOWN:
        on_press(event)
    elif event.event_type == KEY_UP:
        on_release(event)

# Hook the action handler to keyboard events
keyboard.hook(lambda e: on_action(e))

while True:
    time.sleep(0.1)

"""
Alphanumeric keys:

'a' to 'z'
'A' to 'Z'
'0' to '9'
Special keys:

'esc'
'space'
'tab'
'enter'
'shift'
'ctrl'
'alt'
'backspace'
'caps lock'
'num lock'
'scroll lock'
'insert'
'delete'
'home'
'end'
'page up'
'page down'
'up'
'down'
'left'
'right'
'f1' to 'f24'
'print screen'
'pause'
Numpad keys:

'num1' to 'num9'
'num0' (zero)
'num*' (multiplication)
'num/' (division)
'num+' (addition)
'num-' (subtraction)
'num lock' (Num Lock key)
Additional keys (may vary depending on keyboard layout and system):

'browser back'
'browser forward'
'browser refresh'
'browser stop'
'browser search'
'browser favorites'
'browser start and home'
'volume mute'
'volume down'
'volume up'
'media next track'
'media previous track'
'media stop'
'media play/pause'
'launch mail'
'launch media select'
'launch app 1'
'launch app 2'
"""

Code python comme manette de jeux avec IndexIntCmd

EloiStree commented 9 months ago

Java script websocket version

Pousser sur un server websocket deux ints à être rediriger en UDP.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>WebSocket Byte Array Sender</title>
</head>
<body>
  <div id="sentBytesDiv"></div>

  <script>
    // Function to generate a random integer between min and max (inclusive)
    function getRandomInt(min, max) {
      return Math.floor(Math.random() * (max - min + 1)) + min;
    }

    // Function to convert an integer to a byte array with little-endian encoding
    function intToBytesLittleEndian(value) {
      const byteArray = new Uint8Array(4);
      byteArray[0] = value & 0x000000FF;
      byteArray[1] = (value & 0x0000FF00) >> 8;
      byteArray[2] = (value & 0x00FF0000) >> 16;
      byteArray[3] = (value & 0xFF000000) >> 24;
      return byteArray;
    }

    // WebSocket connection
    const socket = new WebSocket('ws://127.0.0.1:7065');

    // Get reference to the div to display sent bytes
    const sentBytesDiv = document.getElementById('sentBytesDiv');

    // Event listener for WebSocket open
    socket.addEventListener('open', function (event) {
      console.log('WebSocket connection established.');

      setInterval(() => {
        const randomInt1 = getRandomInt(0, 255);
        const randomInt2 = getRandomInt(0, 255);

        const byteArr1 = intToBytesLittleEndian(randomInt1);
        const byteArr2 = intToBytesLittleEndian(randomInt2);

        const data = new Uint8Array([...byteArr1, ...byteArr2]);

        socket.send(data);
        console.log('Sent data:', data);

        // Display sent bytes in the HTML document
        sentBytesDiv.innerHTML = `<p>Sent Bytes: ${Array.from(data).join(', ')}</p>`;
      }, 2000);
    });

    // Event listener for WebSocket errors
    socket.addEventListener('error', function (error) {
      console.error('WebSocket error:', error);
    });
  </script>
</body>
</html>

Listen to the joystick of the gamepad in javascript


<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Gamepad Joystick and Button Listener</title>
</head>
<body>
  <script>
    window.addEventListener("gamepadconnected", function(e) {
      console.log("Gamepad connected at index " + e.gamepad.index);
      createAxesList(e.gamepad.index);
      createButtonsList(e.gamepad.index);
      // Start monitoring the gamepad for joystick movements and button presses
      requestAnimationFrame(updateGamepad);
    });

    function createAxesList(index) {
      var axesList = document.createElement('ul');
      axesList.id = 'axesValues_' + index;
      document.body.appendChild(axesList);
    }

    function createButtonsList(index) {
      var buttonsList = document.createElement('ul');
      buttonsList.id = 'buttonsValues_' + index;
      document.body.appendChild(buttonsList);
    }

    function updateGamepad() {
      var gamepads = navigator.getGamepads();
      for (var i = 0; i < gamepads.length; i++) {
        var gamepad = gamepads[i];
        if (gamepad) {
          // Get the axes values
          var axesValues = gamepad.axes;

          // Get the button values
          var buttonsValues = gamepad.buttons;

          // Display the axes values and button values in the corresponding lists
          displayAxesValues(i, axesValues);
          displayButtonsValues(i, buttonsValues);
        }
      }
      // Continue monitoring for joystick movements and button presses
      requestAnimationFrame(updateGamepad);
    }

    function displayAxesValues(index, values) {
      var axesList = document.getElementById('axesValues_' + index);
      if (!axesList) return; // Exit if the list is not found

      axesList.innerHTML = ''; // Clear previous values

      // Create list items for each axis value
      for (var i = 0; i < values.length; i++) {
        var li = document.createElement('li');
        li.textContent = 'Gamepad ' + index + ', Axis ' + i + ': ' + values[i];
        axesList.appendChild(li);
      }
    }

    function displayButtonsValues(index, values) {
      var buttonsList = document.getElementById('buttonsValues_' + index);
      if (!buttonsList) return; // Exit if the list is not found

      buttonsList.innerHTML = ''; // Clear previous values

      // Create list items for each button value
      for (var i = 0; i < values.length; i++) {
        var li = document.createElement('li');
        li.textContent = 'Gamepad ' + index + ', Button ' + i + ': ' + (values[i].pressed ? 'Pressed' : 'Released');
        buttonsList.appendChild(li);
      }
    }
  </script>
</body>
</html>
EloiStree commented 9 months ago

Gamepad and websocket


<!--     Setup the IP and port of the server   .html?ip=127.0.0.1&port=7065     -->
<!--     Setup the IP and port of the server   .html?ip=127.0.0.1&port=7065     -->
<!--     Setup the IP and port of the server   .html?ip=127.0.0.1&port=7065     -->
<!--     Setup the IP and port of the server   .html?ip=127.0.0.1&port=7065     -->
<!--     Setup the IP and port of the server   .html?ip=127.0.0.1&port=7065     -->
<!--     Setup the IP and port of the server   .html?ip=127.0.0.1&port=7065     -->
<!--     Setup the IP and port of the server   .html?ip=127.0.0.1&port=7065     -->
<!--     Setup the IP and port of the server   .html?ip=127.0.0.1&port=7065     -->
<!--     Setup the IP and port of the server   .html?ip=127.0.0.1&port=7065     -->
<!--     Setup the IP and port of the server   .html?ip=127.0.0.1&port=7065     -->
<!--     Setup the IP and port of the server   .html?ip=127.0.0.1&port=7065     -->
<!--     Setup the IP and port of the server   .html?ip=127.0.0.1&port=7065     -->
<!--     Setup the IP and port of the server   .html?ip=127.0.0.1&port=7065     -->
<!--     Setup the IP and port of the server   .html?ip=127.0.0.1&port=7065     -->
<!--     Setup the IP and port of the server   .html?ip=127.0.0.1&port=7065     -->
<!--     Setup the IP and port of the server   .html?ip=127.0.0.1&port=7065     -->
<!--     Setup the IP and port of the server   .html?ip=127.0.0.1&port=7065     -->
<!--     Setup the IP and port of the server   .html?ip=127.0.0.1&port=7065     -->

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Gamepad Joystick and Button Listener</title>
</head>
<body>
    <div id="sentBytesDiv"></div>
  <script>

    // Function to generate a random integer between min and max (inclusive)
    function getRandomInt(min, max) {
      return Math.floor(Math.random() * (max - min + 1)) + min;
    }

    var intIndex0 = getRandomInt(-2000000000,2000000000);
    var intIndex1 = intIndex0+1;
    var intIndex2 = intIndex0+2;
    var intIndex3 = intIndex0+3;
    var intCmd = 1000000001;

    var intCmdPrevious0=1000000001;
    var intCmdPrevious1=1000000001;
    var intCmdPrevious2=1000000001;
    var intCmdPrevious3=1000000001;

    // Function to convert an integer to a byte array with little-endian encoding
    function intToBytesLittleEndian(value) {
      const byteArray = new Uint8Array(4);
      byteArray[0] = value & 0x000000FF;
      byteArray[1] = (value & 0x0000FF00) >> 8;
      byteArray[2] = (value & 0x00FF0000) >> 16;
      byteArray[3] = (value & 0xFF000000) >> 24;
      return byteArray;
    }
    const urlParams = new URLSearchParams(window.location.search);
let param1 = urlParams.get('ip');
let param2 = urlParams.get('port');

// Set default values if parameters are empty or do not exist
param1 = param1 || '127.0.0.1';
param2 = param2 || '7065';

// Construct the WebSocket URL with parameters
const socket = new WebSocket(`ws://${param1}:${param2}`);
    socket.binaryType = 'arraybuffer'; // Set binary type for socket

    // Get reference to the div to display sent bytes
    const sentBytesDiv = document.getElementById('sentBytesDiv');

    function sendIntegersAsBytes(intIndex, intCmd) {
        const byteArr1 = intToBytesLittleEndian(intIndex);
        const byteArr2 = intToBytesLittleEndian(intCmd);

        const data = new Uint8Array([...byteArr1, ...byteArr2]);

        socket.send(data);
        console.log('Sent data:', data);

        // Display sent bytes in the HTML document
        sentBytesDiv.innerHTML = `<p>Sent Bytes: ${Array.from(data).join(', ')}</p>`;
    }

    // Event listener for WebSocket open
    socket.addEventListener('open', function (event) {
      console.log('WebSocket connection established.');

      setInterval(() => {
        const randomInt1 = 555;
        const randomInt2 = 5556;

        const byteArr1 = intToBytesLittleEndian(randomInt1);
        const byteArr2 = intToBytesLittleEndian(randomInt2);

        const data = new Uint8Array([...byteArr1, ...byteArr2]);

        socket.send(data);
        console.log('Sent data:', data);

        // Display sent bytes in the HTML document
        sentBytesDiv.innerHTML = `<p>Sent Bytes: ${Array.from(data).join(', ')}</p>`;
      }, 5000);
    });

    // Event listener for WebSocket errors
    socket.addEventListener('error', function (error) {
      console.error('WebSocket error:', error);
    });

    window.addEventListener("gamepadconnected", function(e) {
      console.log("Gamepad connected at index " + e.gamepad.index);
      createAxesList(e.gamepad.index);
      createButtonsList(e.gamepad.index);
      // Start monitoring the gamepad for joystick movements and button presses
      requestAnimationFrame(updateGamepad);
    });

    function createAxesList(index) {
      var axesList = document.createElement('ul');
      axesList.id = 'axesValues_' + index;
      document.body.appendChild(axesList);
    }

    function createButtonsList(index) {
      var buttonsList = document.createElement('ul');
      buttonsList.id = 'buttonsValues_' + index;
      document.body.appendChild(buttonsList);
    }

    function updateGamepad() {
      var gamepads = navigator.getGamepads();
      for (var i = 0; i < gamepads.length; i++) {
        var gamepad = gamepads[i];
        if (gamepad) {
          // Get the axes values
          var axesValues = gamepad.axes;

          // Get the button values
          var buttonsValues = gamepad.buttons;
          intCmd = 1000000000; // Reset intCmd value

          // Display the axes values and button values in the corresponding lists
          displayAxesValues(i, axesValues);
          displayButtonsValues(i, buttonsValues);
          if(i==0 && intCmdPrevious0!=intCmd){
                intCmdPrevious0=intCmd;
                sendIntegersAsBytes(intIndex0, intCmd);
            }
          if(i==1&& intCmdPrevious1!=intCmd){
                intCmdPrevious1=intCmd;
                sendIntegersAsBytes(intIndex1, intCmd);
            }
          if(i==2&& intCmdPrevious2!=intCmd){
                intCmdPrevious2=intCmd;
                sendIntegersAsBytes(intIndex2, intCmd);
            }
          if(i==3&& intCmdPrevious3!=intCmd){
                intCmdPrevious3=intCmd;
                sendIntegersAsBytes(intIndex3, intCmd);
            }
        }
      }
      // Continue monitoring for joystick movements and button presses
      requestAnimationFrame(updateGamepad);
    }

    function displayAxesValues(index, values) {
      var axesList = document.getElementById('axesValues_' + index);
      if (!axesList) return; // Exit if the list is not found

      axesList.innerHTML = ''; // Clear previous values

      // Create list items for each axis value
      for (var i = 0; i < values.length; i++) {
        var li = document.createElement('li');
        li.textContent = 'Gamepad ' + index + ', Axis ' + i + ': ' + values[i];
        axesList.appendChild(li);
      }
      if(values.length >= 4){
        if(Math.round(values[0]*10)!=0.0)
        intCmd += 10000 * Math.round(((((values[0] + 1.0) /2.0) * 8.0) + 1.0));
        if(Math.round(values[1]*10)!=0.0)
        intCmd += 1000 * Math.round((((1.0-((values[1] + 1.0)/2.0)) * 8.0) + 1.0));
        if(Math.round(values[2]*10)!=0.0)
        intCmd += 100 * Math.round(((((values[2] + 1.0) /2.0) * 8.0) + 1.0));
        if(Math.round(values[3]*10)!=0.0)
        intCmd += 10 * Math.round((((1.0-((values[3] + 1.0) /2.0)) * 8.0) + 1.0));
      }
    }

    function displayButtonsValues(index, values) {
      var buttonsList = document.getElementById('buttonsValues_' + index);
      if (!buttonsList) return; // Exit if the list is not found

      buttonsList.innerHTML = ''; // Clear previous values

      // Create list items for each button value
      for (var i = 0; i < values.length; i++) {
        var li = document.createElement('li');
        li.textContent = 'Gamepad ' + index + ', Button ' + i + ': ' + (values[i].pressed ? 'Pressed' : 'Released');
        buttonsList.appendChild(li);
      }
      if(values.length >= 8){
        if(values[0].pressed ||values[4].pressed  ) intCmd += 100000000;
        if(values[1].pressed ||values[5].pressed  ) intCmd += 10000000;
        if(values[2].pressed ||values[6].pressed  ) intCmd += 1000000;
        if(values[3].pressed ||values[7].pressed  ) intCmd += 100000;
      }
    }
  </script>
</body>
</html>
EloiStree commented 9 months ago

Example de code pour un drone

Example du code d'un voiture

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TankCarRCMono : MonoBehaviour
{

    public Transform m_whatToMove;
    public float m_timepast;
    public float m_carSpeedPerSecond = 0.3f;
    public float m_rotationPerSecondAsAngle = 180f;

    public bool m_makeCarMoveForward;
    public bool m_makeCarMoveBackward;
    public bool m_makeCarRotateLeft;
    public bool m_makeCarRotateRigh;

    void Start()
    {
        Debug.Log("Bonjour");
    }

    void Update()
    {
        float timePast = Time.deltaTime;
        m_timepast = timePast;
        Vector3 whereIsTheCar = m_whatToMove.position;
        Vector3 carForward = m_whatToMove.forward;
        if (m_makeCarMoveForward)
        {
            whereIsTheCar = whereIsTheCar + (carForward * timePast * m_carSpeedPerSecond);
        }
        if (m_makeCarMoveBackward)
        {
            whereIsTheCar = whereIsTheCar - (carForward * timePast * m_carSpeedPerSecond);
        }
        m_whatToMove.position = whereIsTheCar;

        //Quaternion rotation = m_whatToMove.rotation;
        //Quaternion rotatePerSecond = Quaternion.Euler(180f * timePast, 0,0) ;
        Vector3 angleToRotate = new Vector3(0 ,  -m_rotationPerSecondAsAngle * timePast, 0);
        if (m_makeCarRotateLeft)
        {
            m_whatToMove.Rotate(angleToRotate, Space.Self);
        }
        if (m_makeCarRotateRigh)
        {
            m_whatToMove.Rotate(-angleToRotate, Space.Self);
        }

    }

    public void ResetToNotMoving()
    {
        m_makeCarRotateRigh = false;
        m_makeCarRotateLeft = false;
        m_makeCarMoveBackward = false;
        m_makeCarMoveForward = false;
    }

    public void SetCarMoveForward(bool stateOfTheButtonIsOn) {

        m_makeCarMoveForward = stateOfTheButtonIsOn;
    }
    public void SetCarMoveBackward(bool stateOfTheButtonIsOn) { 
        m_makeCarMoveBackward= stateOfTheButtonIsOn;
    }
    public void SetCarRotationLeft(bool stateOfTheButtonIsOn) { 
        m_makeCarRotateLeft= stateOfTheButtonIsOn;
    }
    public void SetCarRotationRight(bool stateOfTheButtonIsOn) {
        m_makeCarRotateRigh= stateOfTheButtonIsOn; 
    }
}

Example de code pour un drone.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DroneRCMono : MonoBehaviour
{

    public Transform m_whatToMove;
    public float m_timepast;
    public float m_droneSpeedPerSecond = 0.3f;
    public float m_rotationPerSecondAsAngle = 180f;

    public bool m_makeDroneRotateRight;
    public bool m_makeDroneRotateLeft;
    public bool m_makeDroneMoveRight;
    public bool m_makeDroneMoveLeft;
    public bool m_makeDroneMoveUp;
    public bool m_makeDroneMoveDown;
    public bool m_makeDroneMoveForward;
    public bool m_makeDroneMoveBackward;

    void Start()
    {
        Debug.Log("Bonjour");
    }

    void Update()
    {
        float timePast = Time.deltaTime;
        m_timepast = timePast;
        Vector3 whereIsTheCar = m_whatToMove.position;
        Vector3 carForward = m_whatToMove.forward;
        Vector3 carUp = m_whatToMove.up;
        Vector3 carRight = m_whatToMove.right;

        if (m_makeDroneMoveBackward)
        {
            whereIsTheCar = whereIsTheCar - (carForward * timePast * m_droneSpeedPerSecond);
        }
        if (m_makeDroneMoveForward)
        {
            whereIsTheCar = whereIsTheCar + (carForward * timePast * m_droneSpeedPerSecond);
        }

        if (m_makeDroneMoveLeft)
        {
            whereIsTheCar = whereIsTheCar - (carRight * timePast * m_droneSpeedPerSecond);
        }
        if (m_makeDroneMoveRight)
        {
            whereIsTheCar = whereIsTheCar + (carRight * timePast * m_droneSpeedPerSecond);
        }
        if (m_makeDroneMoveDown)
        {
            whereIsTheCar = whereIsTheCar - (carUp * timePast * m_droneSpeedPerSecond);
        }
        if (m_makeDroneMoveUp)
        {
            whereIsTheCar = whereIsTheCar + (carUp * timePast * m_droneSpeedPerSecond);
        }
        m_whatToMove.position = whereIsTheCar;

        Vector3 angleToRotate = new Vector3(0 ,  -m_rotationPerSecondAsAngle * timePast, 0);
        if (m_makeDroneRotateLeft)
        {
            m_whatToMove.Rotate(angleToRotate, Space.Self);
        }
        if (m_makeDroneRotateRight)
        {
            m_whatToMove.Rotate(-angleToRotate, Space.Self);
        }

    }

    public void ResetToNotMoving()
    {
        m_makeDroneRotateRight = false;
        m_makeDroneRotateLeft = false;
        m_makeDroneMoveBackward = false;
        m_makeDroneMoveForward = false;
        m_makeDroneMoveUp = false;
        m_makeDroneMoveDown= false;
        m_makeDroneMoveLeft = false;
        m_makeDroneMoveRight= false;
    }

    public void SetCarMoveForward(bool stateOfTheButtonIsOn)
    {

        m_makeDroneMoveForward = stateOfTheButtonIsOn;
    }
    public void SetCarMoveBackward(bool stateOfTheButtonIsOn)
    {
        m_makeDroneMoveBackward = stateOfTheButtonIsOn;
    }

    public void SetCarMoveLeft(bool stateOfTheButtonIsOn)
    {

        m_makeDroneMoveLeft = stateOfTheButtonIsOn;
    }
    public void SetCarMoveRight(bool stateOfTheButtonIsOn)
    {
        m_makeDroneMoveRight = stateOfTheButtonIsOn;
    }

    public void SetCarMoveUp(bool stateOfTheButtonIsOn)
    {

        m_makeDroneMoveUp = stateOfTheButtonIsOn;
    }
    public void SetCarMoveDown(bool stateOfTheButtonIsOn)
    {
        m_makeDroneMoveDown = stateOfTheButtonIsOn;
    }
    public void SetCarRotationLeft(bool stateOfTheButtonIsOn) { 
        m_makeDroneRotateLeft= stateOfTheButtonIsOn;
    }
    public void SetCarRotationRight(bool stateOfTheButtonIsOn) {
        m_makeDroneRotateRight= stateOfTheButtonIsOn; 
    }
}

Old way to capture Keyboard

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

public class ArrowInputType_KeyboardCarOldWayMono : MonoBehaviour
{

    public KeyCode m_up = KeyCode.UpArrow;
    public KeyCode m_down = KeyCode.DownArrow;
    public KeyCode m_left = KeyCode.LeftArrow;
    public KeyCode m_right = KeyCode.RightArrow;
    public BooleanUnityEvent m_onUp;
    public BooleanUnityEvent m_onDown;
    public BooleanUnityEvent m_onLeft;
    public BooleanUnityEvent m_onRight;

    [System.Serializable]
    public class BooleanUnityEvent : UnityEvent<bool> { }

    void Update()
    {

        CheckButtonStateAndSendEvent(m_up, m_onUp);
        CheckButtonStateAndSendEvent(m_down, m_onDown);
        CheckButtonStateAndSendEvent(m_left, m_onLeft);
        CheckButtonStateAndSendEvent(m_right, m_onRight);
    }

    public void CheckButtonStateAndSendEvent(KeyCode button, BooleanUnityEvent eventToSend)
    {
        bool isDown = Input.GetKeyDown(button);
        bool isUp = Input.GetKeyUp(button);
        if (isDown)
            eventToSend.Invoke(true);
        if (isUp)
            eventToSend.Invoke(false);
    }
}
using UnityEngine;
using UnityEngine.Events;

public class ArrowInputType_KeyboardDroneOldWayMono : MonoBehaviour
{

    public KeyCode m_rotateLeft         = KeyCode.LeftArrow;
    public KeyCode m_rotateRight        = KeyCode.RightArrow;
    public KeyCode m_moveUp         = KeyCode.UpArrow;
    public KeyCode m_moveDown       = KeyCode.DownArrow;
    public KeyCode m_moveLeft       = KeyCode.UpArrow;
    public KeyCode m_moveRight      = KeyCode.DownArrow;
    public KeyCode m_moveForward    = KeyCode.LeftArrow;
    public KeyCode m_moveBackward   = KeyCode.RightArrow;

    public BooleanUnityEvent m_onRotateLeft  ;
    public BooleanUnityEvent m_onRotateRight ;
    public BooleanUnityEvent m_onMoveUp      ;
    public BooleanUnityEvent m_onMoveDown    ;
    public BooleanUnityEvent m_onMoveLeft    ;
    public BooleanUnityEvent m_onMoveRight   ;
    public BooleanUnityEvent m_onMoveForward ;
    public BooleanUnityEvent m_onMoveBackward;

    [System.Serializable]
    public class BooleanUnityEvent : UnityEvent<bool> { }

    void Update()
    {

        CheckButtonStateAndSendEvent( m_rotateLeft     , m_onRotateLeft  );
        CheckButtonStateAndSendEvent( m_rotateRight    , m_onRotateRight );
        CheckButtonStateAndSendEvent( m_moveUp         , m_onMoveUp      );
        CheckButtonStateAndSendEvent( m_moveDown       , m_onMoveDown    );
        CheckButtonStateAndSendEvent( m_moveLeft       , m_onMoveLeft    );
        CheckButtonStateAndSendEvent( m_moveRight      , m_onMoveRight   );
        CheckButtonStateAndSendEvent( m_moveForward    , m_onMoveForward );
        CheckButtonStateAndSendEvent(m_moveBackward     , m_onMoveBackward);
    }

    public void CheckButtonStateAndSendEvent(KeyCode button, BooleanUnityEvent eventToSend)
    {
        bool isDown = Input.GetKeyDown(button);
        bool isUp = Input.GetKeyUp(button);
        if (isDown)
            eventToSend.Invoke(true);
        if (isUp)
            eventToSend.Invoke(false);
    }
}
EloiStree commented 9 months ago

Shape and dimension of the drone and car

image

https://github.com/EloiStree/HeRC_ShapeDroneAndCarRC

EloiStree commented 9 months ago

Reserver

EloiStree commented 9 months ago

image https://youtu.be/-P28LKWTzrI

image https://youtu.be/r5NQecwZs1A?t=22

EloiStree commented 9 months ago

reserved

EloiStree commented 9 months ago

image
https://www.youtube.com/watch?v=16VoZuS4iHA&list=PLjoWUDFutpQlFwyefbOWuOYNxzXn23XM2&ab_channel=ÉloiStrée%2Cꬲ🧰%3ARawvideo

Apprendre à faire de la VR sans code :)
En Français.

EloiStree commented 9 months ago

IntCmd pour jouer

Mordhau

image https://www.youtube.com/shorts/vqpJefPGdCA?t=2&feature=share

EloiStree commented 9 months ago

image