[PYTHON] Les débutants veulent créer quelque chose comme un cube rubic avec UE4 et en faire une bibliothèque pour un apprentissage amélioré # 4

Dans la continuité de la dernière fois, il s'agit d'un article dans lequel les débutants continueront à travailler à la création d'une bibliothèque Python qui implique UE4 (presque comme un mémorandum pour moi-même ...).

Premièrement: # 1 Dernière fois: # 3

Essayez d'importer avec un script Python, etc.

Je spécifie l'un des modules Python dans le plan PyActor, mais à ce moment-là, j'ai essayé d'importer les modules Python dans l'autre dossier Script. J'ai l'impression d'avoir essayé de sortir sur la console avec ue.log etc., et il semble que seul celui spécifié dans le plan PyActor soit exécuté. Il semble que l'importation elle-même d'autres modules ne provoque pas d'erreur, mais lorsque j'appelle une fonction ajoutée dans ce module, une erreur se produit. Celui installé avec pip peut être utilisé sans aucun problème, mais en gros, il semble préférable de le considérer comme un module pour un PyActor.

Cependant, si tel est le cas, vous aurez des problèmes avec le traitement courant. Alors, essayez d'ajouter un dossier tel que commun au dossier où la bibliothèque est ajoutée avec pip et importez-le.

Le dossier de la bibliothèque est ajouté par pip de Plugins \ UnrealEnginePython \ Binaries \ Win64 Un dossier appelé common est ajouté au répertoire du plugin, et un fichier appelé sqlite_utils.py est ajouté en supposant qu'un traitement commun pour les éléments liés à SQLite est écrit. Essayez la description suivante dans ce fichier.

sqlite_utils.py


def print_test(ue):
    ue.log('sqlite_utils test')

Ensuite, ajoutez la description suivante dans le module spécifié par PyActor.

import unreal_engine as ue

from common import sqlite_utils

sqlite_utils.print_test(ue=ue)

Le répertoire géré par pip devrait être dans le chemin, donc je l'essaye en supposant qu'il devrait pouvoir être importé. Voyons un aperçu dans UE4 (ou plutôt, Play est le mot correct ...).

image.png

Il semble que le module importé soit appelé avec succès. Lorsque vous devez écrire du Python qui couvre plusieurs modules, il semble bon d'ajouter le module au dossier de la bibliothèque.

De plus, au lieu d'importer directement unreal_engine dans le module common.sqlite_utils, il est passé en tant qu'argument de fonction à partir du module spécifié par PyActor, mais si ce n'est pas le module spécifié par PyActor, il fonctionnera également comme ue.log. pas. Peut-être que vous ne pouvez pas utiliser l'importation du module unreal_engine autre que le module spécifié par PyActor. Par conséquent, si le module unreal_engine est requis du côté du module commun, passez-le comme argument cette fois.

Réfléchissez à ce qu'il faut faire avec le test des scripts Python

Pour le développement, j'aimerais écrire un test avec un script Python. Cependant, comme mentionné ci-dessus, il existe une restriction selon laquelle il y a un module pour chaque classe PyActor, et ceux impliquant le module unreal_engine ne peuvent pas être testés sans jouer sur UE4, donc un exécuteur de test tel que pytest ne peut pas être utilisé.

Donc, bien que cela semble irrégulier, je vais procéder comme suit.

Pour le moment, nous allons commencer avec notre propre testeur.

py:common.python_test_runner.py


"""Un module qui gère l'exécution des tests pour chaque script Python.

Notes
-----
- unreal_Pour la commodité d'entrelacer le module moteur, sans séparer le module de test
Fonctionner.
"""

from datetime import datetime
import inspect


def run_tests(ue, target_module):
    """
Exécutez les tests définis pour le module cible.

    Parameters
    ----------
    ue : unreal_engine
Importé dans le module Python spécifié par chaque PyActor,
Module de bibliothèque Python UnrealEngine.
    target_module : module
Le module à tester. Test en module_Du préfixe
La fonction est exécutée.
    """
    ue.log(
        '%s %Commencez à tester le module s...'
        % (datetime.now(), target_module.__name__))
    members = inspect.getmembers(target_module)
    for obj_name, obj_val in members:
        if not inspect.isfunction(obj_val):
            continue
        if not obj_name.startswith('test_'):
            continue
        ue.log(
            '%Fonction cible: %s' % (datetime.now(), obj_name))
        pre_dt = datetime.now()
        obj_val()
        timedelta = datetime.now() - pre_dt
        ue.log('%s ok. %s secondes' % (datetime.now(), timedelta.total_seconds()))

J'ajusterai les détails plus tard, mais une fois que c'est une implémentation simple, tout va bien. Avec la fonction getmembers du module d'inspection intégré, vous pouvez prendre l'élément membre du module spécifié dans l'argument, donc tournez-le dans une boucle, vérifiez s'il s'agit d'une fonction avec la fonction isfunction, et le traitement de flux uniquement lorsque le nom de la fonction a un préfixe de test_ Je le fais. Le reste n'est que la description qui renvoie l'heure du test, le nom du module cible, le nom de la fonction, etc. vers la console.

Séparément, dans le module spécifié par PyActor, préparez un module d'écriture de SQLite pour transmettre des données dans le flux de la bibliothèque UE4 → Python. Là, j'ai préparé une fonction supplémentaire appelée add pour vérifier le fonctionnement du test runner, que j'effacerai plus tard.

to_python_sqlite_writer.py


"""Gère l'écriture de données depuis UE4 vers SQLite pour les bibliothèques Python
Module pour.
"""

import sys
import importlib
import time
import unittest

import unreal_engine as ue

from common import python_test_runner

importlib.reload(python_test_runner)


def add(a, b):
    time.sleep(1)
    return a + b


def test_add():
    added_val = add(a=1, b=3)
    assert added_val == 4


python_test_runner.run_tests(
    ue=ue, target_module=sys.modules[__name__])

Pour vérifier l'affichage du temps de test, je l'ai volontairement mis en veille pendant 1 seconde dans la fonction. Comme mentionné ci-dessus, j'essaye d'écrire des tests dans le même module. Enfin, le processus de test runner est appelé au niveau supérieur. Vous pouvez obtenir votre propre module en faisant sys.moduels [__name __].

De plus, le module spécifié par PyActor est automatiquement rechargé du côté UE4 après la mise à jour du code (car la fenêtre contextuelle de configuration apparaît), mais il semble que le module commun, etc. ne soit pas rechargé automatiquement. Par conséquent, le changement de code peut ne pas être reflété immédiatement, donc je le recharge avec importlib pour une réflexion immédiate (peut-être qu'il sera supprimé à la fin ...).

Jouons avec UE4.

image.png

Le test a réussi. J'ajusterai les détails plus tard si nécessaire, mais pour le moment, cela semble correct ...

Mettez le nez dans la bibliothèque de tests

Récemment, lorsque j'écris du code en privé, j'utilise souvent pytest en raison de l'excellence des testeurs, mais cette fois je n'ai besoin que des fonctions assert (assert_equal et assert_raises), donc pour faciliter les choses, la bibliothèque de test de nose Je vais le mettre.

$ ./python.exe -m pip install --target . nose
Successfully installed nose-1.3.7

Pour m'assurer que cela fonctionne sur UE4, je vais ajuster le code que j'ai écrit dans la validation du test il y a quelque temps.

to_python_sqlite_writer.py


from nose.tools import assert_equal
...
def test_add():
    added_val = add(a=1, b=3)
    assert_equal(added_val, 4)

image.png

Cela semble marcher correctement.

Continuons avec la partie lecture et écriture dans SQLite

La dernière fois, j'ai mis SQLAlchemy pour SQLite et je l'ai soutenu au point où l'importation, etc. peut être effectuée au moins, mais je vais confirmer qu'il n'y a pas de problème de vérification un peu plus et organiser les fichiers provisoires. Tout d'abord, supprimez python_and_h5py_test.py utilisé pour la vérification, comme h5py et la classe de plan qui lui est associée.

Tout d'abord, ajoutez le processus pour obtenir la chaîne de caractères pour spécifier le chemin de SQLAlchemy de SQLite dans le module commun.

common\sqlite_utils.py


"""Un module qui décrit les traitements courants liés à SQLite.
"""

import os
import sys

DESKTOP_FOLDER_NAME = 'cubicePuzzle3x3'


def get_sqlite_engine_file_path(file_name):
    """
Obtenez le chemin du fichier pour spécifier le moteur de SQL Alchemy dans SQLite.

    Parameters
    ----------
    file_name : str
Le nom du fichier SQL cible qui contient l'extension.

    Returns
    -------
    sqlite_file_path : str
Une chaîne de chemins pour spécifier le moteur de SQLite.
        sqlite:///Un dossier pour SQLite est créé sur le bureau à partir de
Mettre en forme.

    Notes
    -----
Il est créé si le dossier de destination de l'enregistrement n'existe pas.
    """
    dir_path = os.path.join(
        os.environ['HOMEPATH'], 'Desktop', DESKTOP_FOLDER_NAME)
    os.makedirs(dir_path, exist_ok=True)
    sqlite_file_path = 'sqlite:///{dir_path}/{file_name}'.format(
        dir_path=dir_path,
        file_name=file_name,
    )
    return sqlite_file_path

Au fur et à mesure que je l'ai écrit, j'ai remarqué que je ne peux pas écrire de test pour un module commun (ce module lui-même n'importe pas le module ue).

Par conséquent, ajoutez un module pour exécuter le test du module commun et une classe blueprint de PyActor, et exécutez le test de chaque module commun via cela.

Content\Scripts\run_common_module_tests.py


"""Pour les modules sous le répertoire commun du plug-in Python
Exécutez le test.
"""

import sys
import inspect

import unreal_engine as ue

from common import python_test_runner
from common.tests import test_sqlite_utils

NOT_TEST_TARGET_MODULES = [
    sys,
    inspect,
    ue,
    python_test_runner,
]

members = inspect.getmembers(sys.modules[__name__])
for obj_name, obj_val in members:
    if not inspect.ismodule(obj_val):
        continue
    is_in = obj_val in NOT_TEST_TARGET_MODULES
    if is_in:
        continue
    python_test_runner.run_tests(ue=ue, target_module=obj_val)

Le côté BP l'a nommé BP_RunCommonModuleTests.

J'écrirai un test. Pour le module commun, l'importation du module ue est inutile en raison du code ci-dessus, et il n'y a pas de problème même si les modules sont séparés, donc test_ <nom du module avec le répertoire du module de test pris en sandwich comme un test normal Nous procéderons sous la forme de> .py.

common\tests\test_sqlite_utils.py


"""sqlite_Un module pour tester le module utils.
"""

from nose.tools import assert_equal, assert_true

from common import sqlite_utils


def test_get_sqlite_engine_file_path():
    sqlite_file_path = sqlite_utils.get_sqlite_engine_file_path(
        file_name='test_dbfile.sqlite')
    assert_true(
        sqlite_file_path.startswith('sqlite:///')
    )
    is_in = sqlite_utils.DESKTOP_FOLDER_NAME in sqlite_file_path
    assert_true(is_in)
    assert_true(
        sqlite_file_path.endswith('/test_dbfile.sqlite')
    )

Jouons à UE4.

image.png

Ça me va. Vous pouvez désormais également écrire des tests du côté du module commun. Le problème est qu'il n'est pas possible de tester uniquement des modules spécifiques ou des fonctions spécifiques, qui sont préparés par pytest etc. Je penserai à ce point lorsque le nombre de tests augmente et que cela me semble assez gênant (par exemple, permettre de tester rapidement uniquement des modules spécifiques). Il y a de fortes chances que le temps de test reste à un niveau qui ne vous dérange pas tant que l'implémentation n'est pas terminée ...

Vérifiez le comportement pour tester SQLite, envisagez le traitement d'initialisation et le test lorsqu'une classe est prise en sandwich entre les scripts PyActor

Lorsque je travaillais sur UE4 et que je testais avec SQLite, il y avait un cas où le fichier SQLite restait verrouillé même si Play était à nouveau exécuté (par exemple lorsqu'une erreur s'est produite au milieu). ).

Lorsqu'elle est réellement emballée, il semble que l'application sera supprimée une fois, mais pendant le développement, il est difficile de redémarrer UE4 à chaque fois pour la déverrouiller. Assurez-vous d'insérer les informations de date et d'heure au moment où vous appuyez sur Lecture dans le nom du fichier afin qu'il s'agisse d'un fichier SQLite différent à chaque lecture. Ajoutez le nom initializer.py et le plan PyActor.

Content\Scripts\initializer.py


"""Un module qui décrit le processus qui est exécuté en premier, comme le démarrage du jeu.
"""

import json
from datetime import datetime
import os
import sys
import time
import importlib

from nose.tools import assert_equal, assert_true
import unreal_engine as ue

from common import const, file_helper
from common.python_test_runner import run_tests
importlib.reload(const)
importlib.reload(file_helper)


def save_session_data_json():
    """
Pour conserver les informations au début d'une seule session de jeu
Enregistrez le fichier JSON.
    """
    session_data_dict = {
        const.SESSION_DATA_KEY_START_DATETIME: str(datetime.now()),
    }
    file_path = file_helper.get_session_json_file_path()
    with open(file_path, mode='w') as f:
        json.dump(session_data_dict, f)
    ue.log('initialized.')


save_session_data_json()


def test_save_session_data_json():
    pre_session_json_file_name = const.SESSION_JSON_FILE_NAME
    const.SESSION_JSON_FILE_NAME = 'test_game_session.json'

    expected_file_path = file_helper.get_session_json_file_path()
    save_session_data_json()
    assert_true(os.path.exists(expected_file_path))
    with open(expected_file_path, 'r') as f:
        json_str = f.read()
    data_dict = json.loads(json_str)
    expected_key_list = [
        const.SESSION_DATA_KEY_START_DATETIME,
    ]
    for key in expected_key_list:
        has_key = key in data_dict
        assert_true(has_key)

    os.remove(expected_file_path)
    const.SESSION_JSON_FILE_NAME = pre_session_json_file_name


run_tests(
    ue=ue,
    target_module=sys.modules[__name__])

Nous ajouterons également un module commun pour les opérations sur les fichiers et ses tests.

Win64\common\file_helper.py


"""Un module qui décrit les traitements courants liés aux opérations sur les fichiers.
"""

import os
import time
import json
from datetime import datetime

from common.const import DESKTOP_FOLDER_NAME
from common import const


def get_desktop_data_dir_path():
    """
Obtenez un répertoire pour enregistrer les données sur le bureau.

    Returns
    -------
    dir_path : str
Chemin du répertoire pour stocker les données de bureau récupérées.

    Notes
    -----
Il est créé si le dossier de destination de l'enregistrement n'existe pas.
    """
    dir_path = os.path.join(
        os.environ['HOMEPATH'], 'Desktop', DESKTOP_FOLDER_NAME)
    os.makedirs(dir_path, exist_ok=True)
    return dir_path


def get_session_json_file_path():
    """
Pour conserver les informations au début d'une seule session de jeu
Obtenez le chemin du fichier JSON.

    Returns
    -------
    file_path : str
Chemin du fichier cible.
    """
    file_path = os.path.join(
        get_desktop_data_dir_path(),
        const.SESSION_JSON_FILE_NAME
    )
    return file_path


def get_session_start_time_str(remove_symbols=True):
    """
Récupérez la chaîne de date et d'heure au début d'une session de jeu à partir du fichier JSON.
Il est utilisé pour les noms de fichiers SQLite.

    Parameters
    ----------
    remove_symbols : bool, default True
Supprimez le symbole de la chaîne de valeur de retour pour en faire une valeur avec uniquement des entiers demi-largeur
Que ce soit pour convertir.

    Returns
    -------
    session_start_time_str : str
Une chaîne de dates et d'heures au début d'une session de jeu.
    """
    time.sleep(0.1)
    file_path = get_session_json_file_path()
    with open(file_path, mode='r') as f:
        data_dict = json.load(f)
    session_start_time_str = str(
        data_dict[const.SESSION_DATA_KEY_START_DATETIME])
    if remove_symbols:
        session_start_time_str = session_start_time_str.replace('-', '')
        session_start_time_str = session_start_time_str.replace('.', '')
        session_start_time_str = session_start_time_str.replace(':', '')
        session_start_time_str = session_start_time_str.replace(' ', '')
    return session_start_time_str

Ajoutez également un module pour le traitement commun SQLite.

Win64\common\sqlite_utils.py


"""Un module qui décrit les traitements courants liés à SQLite.
"""

import os
import sys

import sqlalchemy
from sqlalchemy.orm import sessionmaker

from common import file_helper


def get_sqlite_engine_file_path(file_name):
    """
Obtenez le chemin du fichier pour spécifier le moteur de SQL Alchemy dans SQLite.

    Parameters
    ----------
    file_name : str
Le nom du fichier SQL cible qui contient l'extension.

    Returns
    -------
    sqlite_file_path : str
Une chaîne de chemins pour spécifier le moteur de SQLite.
        sqlite:///Un dossier pour SQLite est créé sur le bureau à partir de
Mettre en forme.

    Notes
    -----
Il est créé si le dossier de destination de l'enregistrement n'existe pas.
    """
    dir_path = file_helper.get_desktop_data_dir_path()
    sqlite_file_path = 'sqlite:///{dir_path}/{file_name}'.format(
        dir_path=dir_path,
        file_name=file_name,
    )
    return sqlite_file_path


def create_session(sqlite_file_name, declarative_meta):
    """
Créez une session SQLite.

    Parameters
    ----------
    sqlite_file_name : str
Le nom du fichier SQLite cible.
    declarative_meta : DeclarativeMeta
Un objet qui stocke les métadonnées pour chaque table dans le SQLite cible.

    Returns
    -------
    session : Session
La session SQLite générée.
    """
    sqlite_file_path = get_sqlite_engine_file_path(
        file_name=sqlite_file_name)
    engine = sqlalchemy.create_engine(sqlite_file_path, echo=True)
    declarative_meta.metadata.create_all(bind=engine)
    Session = sessionmaker(bind=engine)
    session = Session()
    return session

Win64\common\tests\test_sqlite_utils.py


"""sqlite_Un module pour tester le module utils.
"""

import os
from nose.tools import assert_equal, assert_true
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer

from common import sqlite_utils, file_helper


def test_get_sqlite_engine_file_path():
    sqlite_file_path = sqlite_utils.get_sqlite_engine_file_path(
        file_name='test_dbfile.sqlite')
    assert_true(
        sqlite_file_path.startswith('sqlite:///')
    )
    is_in = file_helper.DESKTOP_FOLDER_NAME in sqlite_file_path
    assert_true(is_in)
    assert_true(
        sqlite_file_path.endswith('/test_dbfile.sqlite')
    )


def test_create_session():
    if not os.path.exists(file_helper.get_session_json_file_path()):
        return
    session_start_time_str = file_helper.get_session_start_time_str()
    sqlite_file_name = 'test_%s.sqlite' % session_start_time_str
    expected_file_path = sqlite_utils.get_sqlite_engine_file_path(
        file_name=sqlite_file_name)
    if os.path.exists(expected_file_path):
        os.remove(expected_file_path)

    declarative_meta = declarative_base()

    class TestTable(declarative_meta):
        id = Column(Integer, primary_key=True)
        __tablename__ = 'test_table'

    session = sqlite_utils.create_session(
        sqlite_file_name=sqlite_file_name,
        declarative_meta=declarative_meta)

    test_data = TestTable()
    session.add(instance=test_data)
    session.commit()
    query_result = session.query(TestTable)
    for test_data in query_result:
        assert_true(isinstance(test_data.id, int))

    expected_file_path = expected_file_path.replace('sqlite:///', '')
    assert_true(
        os.path.exists(expected_file_path))

    session.close()
    os.remove(expected_file_path)

Bien qu'il soit correct d'insérer initializer.py, lors de son utilisation réelle, il est nécessaire que le processus du module connecté à SQLite soit exécuté après l'initialisation. D'un autre côté, on ne sait pas quel module sera exécuté en premier dans PyActor (dans certains cas, les scripts de niveau supérieur d'autres modules seront exécutés avant l'initialisation).

Par conséquent, pour ce qui doit être fait après l'initialisation, spécifiez la classe avec PyActor et démarrez-la via la méthode begin_play (via begin_play, elle sera exécutée après le traitement de niveau supérieur de chaque module. On dirait, au moins autant que j'ai essayé).

Content\Scripts\to_python_sqlite_writer.py


"""Gère l'écriture de données depuis UE4 vers SQLite pour les bibliothèques Python
Module pour.
"""

import sys
import importlib
import time
import unittest
import time

import unreal_engine as ue
from nose.tools import assert_equal, assert_true
import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String

from common import python_test_runner, sqlite_utils, file_helper

importlib.reload(python_test_runner)
importlib.reload(sqlite_utils)
declarative_meta = declarative_base()


class ToPythonSqliteWriter:

    class TestTable(declarative_meta):
        id = Column(Integer, primary_key=True)
        name = Column(String(length=256))
        __tablename__ = 'test_table'

    def begin_play(self):
        """
Une fonction qui est exécutée lorsque le jeu Play démarre.

        Notes
        -----
Il est exécuté après le traitement de niveau supérieur de chaque module.
        """
        self.session_start_time_str = \
            file_helper.get_session_start_time_str()
        self.SQLITE_FILE_NAME = 'to_python_from_ue4_%s.sqlite'\
            % self.session_start_time_str

        self.session = sqlite_utils.create_session(
            sqlite_file_name=self.SQLITE_FILE_NAME,
            declarative_meta=declarative_meta,
        )
        python_test_runner.run_begin_play_test(
            begin_play_test_func=self.test_begin_play
        )

    def test_begin_play(self):
        assert_equal(
            self.session_start_time_str,
            file_helper.get_session_start_time_str(),
        )
        is_in = 'to_python_from_ue4_' in self.SQLITE_FILE_NAME
        assert_true(is_in)
        is_in = '.sqlite' in self.SQLITE_FILE_NAME
        assert_true(is_in)
        query_result = self.session.query(self.TestTable)


python_test_runner.run_tests(
    ue=ue, target_module=sys.modules[__name__])

De plus, comme le testeur actuellement ajouté ne prend en charge que les tests de niveau supérieur, nous insérerons une fonction appelée run_begin_play_test pour exécuter un test pour la méthode liée à l'événement UE4 spécial de begin_play.

Win64\common\python_test_runner.py


def run_begin_play_test(begin_play_test_func):
    """
Début de la classe spécifiée par PyActor_Test de la méthode de jeu
Exécuter.

    Parameters
    ----------
    begin_play_test_func : function
Début de la cible_Testez la méthode de lecture.
    """
    begin_play_test_func()

Actuellement, il n'y a presque pas de contenu, mais nous allons insérer un processus pour qu'il ne soit exécuté que pendant le développement plus tard (car nous n'avons pas encore été en mesure de coopérer avec l'UE4 dans cette mesure).

Vérifiez le fichier SQLite généré

Si vous vérifiez que le test réussit et que vous vérifiez le bureau, vous pouvez voir que le fichier a été généré pour le moment.

image.png

Vérifions un peu le contenu. Installez DB Browser for SQLite et vérifiez le contenu.

image.png

Pour le moment, SQLite via UE4 semble fonctionner correctement.

Autoriser Python à prendre la valeur de savoir s'il s'agit d'un environnement packagé

Du côté Python également, permettez de prendre la valeur du nœud Is Packaged for Distribution afin que les tests etc. ne soient exécutés que pendant le développement. Cependant, comme l'appel de fonction ne peut être effectué que via uobject, il doit passer par la classe et il ne peut pas être traité au niveau supérieur de l'initialiseur. Eh bien, il s'agit simplement de réduire les traitements inutiles, et ce n'est pas quelque chose qui peut être fait, alors continuons sans nous soucier des détails ...

Puisque nous devons appeler la fonction sur le plan UE4 à partir de Python, ajoutez d'abord la fonction cible au plan directeur.

image.png

C'est une fonction qui renvoie simplement la valeur de la fonction préparée. Est-il possible d'appeler une fonction directement préparée (verte au lieu de violet) depuis Python sans cette fonction? J'ai essayé, mais il a été joué sans une telle chose, donc je vais le préparer normalement.

Spécifiez également la classe sur le plan.

image.png

Nous allons procéder avec le support côté Python. Nous devons parcourir le uobjet de la classe, alors préparez la classe.

Content\Scripts\initializer.py


class Initializer:

    def begin_play(self):
        """
Une fonction qui est exécutée lorsque le jeu Play démarre.
        """
        self.is_packaged_for_distribution = \
            self.uobject.isPackagedForDistribution()[0]
        _update_packaged_for_distribution_value(
            is_packaged_for_distribution=self.is_packaged_for_distribution)


def _update_packaged_for_distribution_value(is_packaged_for_distribution):
    """
Si l'environnement est conditionné pour la distribution (pour la production)
Mettez à jour la valeur booléenne.

    Parameters
    ----------
    is_packaged_for_distribution : bool
Valeur booléenne à définir.
    """
    file_path = file_helper.get_session_json_file_path()
    if os.path.exists(file_path):
        with open(file_path, mode='r') as f:
          session_data_dict = json.load(f)
    else:
        session_data_dict = {}
    if is_packaged_for_distribution:
        is_packaged_for_distribution_int = 1
    else:
        is_packaged_for_distribution_int = 0
    with open(file_path, mode='w') as f:
        session_data_dict[
            const.SESSION_DATA_KEY_IS_PACKAGED_FOR_DISTRIBUTION] = \
            is_packaged_for_distribution_int
        json.dump(session_data_dict, f)


def test__update_packaged_for_distribution_value():
    pre_session_json_file_name = const.SESSION_JSON_FILE_NAME
    const.SESSION_JSON_FILE_NAME = 'test_game_session.json'
    expected_file_path = file_helper.get_session_json_file_path()

    _update_packaged_for_distribution_value(
        is_packaged_for_distribution=True)
    with open(expected_file_path, mode='r') as f:
        session_data_dict = json.load(f)
    assert_equal(
        session_data_dict[
            const.SESSION_DATA_KEY_IS_PACKAGED_FOR_DISTRIBUTION],
        1
    )

    _update_packaged_for_distribution_value(
        is_packaged_for_distribution=False)
    with open(expected_file_path, mode='r') as f:
        session_data_dict = json.load(f)
    assert_equal(
        session_data_dict[
            const.SESSION_DATA_KEY_IS_PACKAGED_FOR_DISTRIBUTION],
        0
    )

    os.remove(expected_file_path)
    const.SESSION_JSON_FILE_NAME = pre_session_json_file_name

De plus, bien que la valeur soit prise comme self.uobject.isPackagedForDistribution () [0], même s'il n'y a qu'une seule valeur de retour de la fonction blueprint, elle est passée sous forme de taple côté Python, donc c'est fait. Je vais. Même si je produis sur la console sur UE4, c'est faux, mais je dois m'inquiéter que la branche ne fonctionne pas pendant quelques minutes. Apparemment, il semble que la sortie de la console soit directement comme False ou True au lieu de l'affichage taple comme (False,). C'est déroutant ... (au moins une virgule ...) Quand j'ai affiché le type, j'ai remarqué que c'était un tuple.

J'ajouterai également le processus d'acquisition de valeur.

Win64\common\file_helper.py


def get_packaged_for_distribution_bool():
    """
Obtient la valeur booléenne indiquant s'il est dans un état empaqueté pour la distribution.

    Notes
    -----
Seulement au début du premier démarrage, le moment où la valeur ne peut pas être obtenue normalement avant d'enregistrer la valeur
Existe. Dans ce cas, False sera retourné.

    Returns
    -------
    is_packaged_for_distribution : bool
Vrai et emballé pour la distribution (pour la production), Faux
Pour le developpement.
    """
    file_path = get_session_json_file_path()
    if not os.path.exists(file_path):
        return False
    with open(file_path, mode='r') as f:
        json_str = f.read()
        if json_str == '':
            session_data_dict = {}
        else:
            session_data_dict = json.loads(json_str)
    has_key = const.SESSION_DATA_KEY_IS_PACKAGED_FOR_DISTRIBUTION \
        in session_data_dict
    if not has_key:
        return False
    is_packaged_for_distribution = session_data_dict[
        const.SESSION_DATA_KEY_IS_PACKAGED_FOR_DISTRIBUTION]
    if is_packaged_for_distribution == 1:
        return True
    return False

Win64\common\tests\test_file_helper.py


def test_get_packaged_for_distribution_bool():
    pre_session_json_file_name = file_helper.const.SESSION_JSON_FILE_NAME
    file_helper.const.SESSION_JSON_FILE_NAME = 'test_game_session.json'
    file_path = file_helper.get_session_json_file_path()
    if os.path.exists(file_path):
        os.remove(file_path)

    #Vérifiez la valeur de retour si le fichier n'existe pas.
    is_packaged_for_distribution = \
        file_helper.get_packaged_for_distribution_bool()
    assert_false(is_packaged_for_distribution)

    #Si le fichier existe, mais que des caractères vides sont définis
    #Vérifiez la valeur renvoyée.
    with open(file_path, 'w') as f:
        f.write('')
    is_packaged_for_distribution = \
        file_helper.get_packaged_for_distribution_bool()
    assert_false(is_packaged_for_distribution)

    #Vérifiez la valeur de retour lorsque la valeur est définie sur 1.
    with open(file_path, 'w') as f:
        session_data_dict = {
            const.SESSION_DATA_KEY_IS_PACKAGED_FOR_DISTRIBUTION: 1,
        }
        json.dump(session_data_dict, f)
    is_packaged_for_distribution = \
        file_helper.get_packaged_for_distribution_bool()
    assert_true(
        is_packaged_for_distribution
    )

    #Vérifiez la valeur de retour lorsque la valeur est définie sur 0.
    with open(file_path, 'w') as f:
        session_data_dict = {
            const.SESSION_DATA_KEY_IS_PACKAGED_FOR_DISTRIBUTION: 0,
        }
        json.dump(session_data_dict, f)
    is_packaged_for_distribution = \
        file_helper.get_packaged_for_distribution_bool()
    assert_false(is_packaged_for_distribution)

    os.remove(file_path)
    file_helper.const.SESSION_JSON_FILE_NAME = pre_session_json_file_name

Placez des branches dans différentes parties du testeur comme indiqué ci-dessous.

Win64\common\python_test_runner.py


def run_tests(ue, target_module):
    ...
    is_packaged_for_distribution = \
        file_helper.get_packaged_for_distribution_bool()
    if is_packaged_for_distribution:
        return
    ...

Refléter l'état aléatoire de l'affichage initial

Actuellement, il commence avec les couleurs alignées, alors faites-le pivoter au hasard pour lui donner l'impression d'un cube rubic normal.

Nous avons un processus de rotation immédiat qui ne s'anime pas, nous allons donc l'utiliser pour ajouter des fonctions au plan. De plus, comme le nom de la fonction est réinitialisé dans la bibliothèque Gym d'OpenAI pour le processus de réinitialisation de l'environnement, définissez-le comme réinitialisé en fonction de cela (il est utilisé non seulement au début, mais également lors du déplacement à nouveau).

Tout d'abord, obtenez la valeur du nombre de tours à tourner. Il semble que quelque chose comme le randint de NumPy soit préparé dans le nœud Random Integer, alors utilisez-le. Il semble que la valeur minimale est définie sur 0 et la valeur maximale est définie sur Max -1. De plus, si elle est laissée telle quelle, lorsque 0 sort, la surface sera alignée depuis le début ..., elle doit donc être tournée au moins 200 fois sur le nœud MAX.

image.png

Préparez une valeur booléenne en tant que variable locale pour déterminer dans quelle direction tourner dans la boucle. Définissez-le également sur False au début de la boucle.

image.png

Tout d'abord, XYZ La valeur de vérité de la direction de rotation est déterminée au hasard comme étant True. Essayez d'utiliser le nœud Switch pour la première fois. Si vous êtes habitué à la programmation, vous pouvez l'utiliser sans aucun problème.

image.png

Puis le sens de rotation. Décidez au hasard de la rotation vers la gauche ou vers la droite, ou vers le haut ou vers le bas.

image.png

Enfin, calculez la valeur telle que la colonne à faire pivoter. Puisqu'il est nécessaire de calculer une valeur de 1 à 3, la valeur de 0 à 2 est sortie par le noeud Random Integer puis incrémentée de 1.

image.png

Après cela, branchez avec Branch selon la valeur déterminée et exécutez la rotation requise, et c'est terminé.

image.png

Je vais le déplacer.

image.png

La couleur s'est bien détachée. Je ne sais pas comment le faire pivoter pour que toutes les couleurs soient alignées.

Essayez de l'exécuter à nouveau.

image.png

C'est différent du précédent. Ça me va. C'est un processus avec beaucoup de boucles, mais dans mon environnement de bureau, cela se termine en un instant, et il n'est pas nécessaire de maintenir le FPS en particulier, et en premier lieu, la personne qui travaille avec un apprentissage amélioré par l'apprentissage est le bureau, et un PC avec des spécifications raisonnablement bonnes La plupart le seront, donc je pense qu'il n'y a pas de problème.

Préparez un processus pour acquérir la valeur booléenne indiquant s'il tourne ou non.

Je suis ennuyé si une autre rotation est effectuée pendant l'animation. Finalement, j'essaierai de contrôler les erreurs du côté Python, mais pour le moment, j'ajouterai une implémentation sur le plan et j'écrirai le code Python en coopération avec cela.

Le nom sera créé avec un plan qui hérite de PyActor nommé BP_Action selon les termes de la bibliothèque Gym.

Il n'y a pas de mal à ajouter un plan, mais au fait, comment mettre les acteurs au niveau ...? Quand j'ai réfléchi, c'était écrit dans le document officiel.

Recherche d'acteurs par plan

Il semble que vous puissiez utiliser le nœud Get All Actors Of Class, je vais donc l'essayer.

image.png

C'est un nœud qui tourne simplement en boucle et imprime le nom à l'écran.

image.png

Il s'affiche correctement. Facile.

Avec cela, il semble que nous pouvons préparer une fonction pour prendre la valeur de savoir si l'acteur du cube en rotation existe ou non dans le plan de BP_Action.

Ajoutez une fonction appelée isRotating au plan directeur de la classe de base de BP_CubeBase.

Puisque nous avions préparé une fonction pour obtenir la valeur de vérité de la rotation cible dans chaque sens de rotation auparavant, nous préparerons un tableau de valeurs de vérité avec des variables locales et intégrerons le tableau en l'appelant. Il semble que vous puissiez ajouter un tableau à un tableau avec le nœud APPEND (comportement de type extension en Python).

image.png

Lorsque j'ai fini d'ajouter un ensemble de valeurs booléennes au tableau, j'avais l'habitude de préparer une fonction à un autre endroit qui renvoie True si l'une des valeurs booléennes est True dans le tableau en passant le tableau (that). J'ai spécifié 3 séquences, mais ...), donc je vais l'utiliser.

image.png

Créez un plan pour BP_Action avec une fonction nommée isAnyCubeRotating.

image.png

Utilisez Get All Actors Of Class, que j'ai mentionné il y a quelque temps, pour obtenir un tableau et le boucler.

image.png

S'il y a un cube qui tourne dans la boucle, il sera envoyé au nœud de retour vrai, et si la boucle est terminée sans en trouver un en rotation, il sera envoyé au nœud de retour faux.

En outre, dans le modèle BP_Action, spécifiez le module et la classe Python.

image.png

Ajoutez un module appelé action.py côté Python et écrivez le processus. Je n'ai pas encore écrit le processus de rotation etc. du côté Python, donc je vais sauter le test etc. une fois (je l'écrirai si le test fonctionnel lié est préparé, mais je comprends bien qu'il n'est pas maintenu et lié à UE4 Parce que non). Essayez également la sortie de la console avec tick (pour l'instant, elle doit toujours renvoyer False).

Content\Scripts\action.py


"""Un module qui décrit certains traitements liés au contrôle du comportement dans l'agent.
"""

import unreal_engine as ue


class Action:

    def tick(self, delta_time):
        """
Une fonction qui est exécutée à peu près toutes les images pendant le jeu.

        Parameters
        ----------
        delta_time : float
Secondes écoulées depuis le dernier appel de tick.
        """
        ue.log(is_any_cube_rotating(action_instance=self))


def is_any_cube_rotating(action_instance):
    """
Obtient la valeur booléenne indiquant si un cube est en train de tourner.

    Parameters
    ----------
    action_instance : Action
Une instance de la classe Action avec uobject.

    Returns
    ----------
    is_rotating : bool
True est défini si un cube tourne.
    """
    is_rotating = action_instance.uobject.isAnyCubeRotating()[0]
    return is_rotating

Si vous regardez le journal, vous pouvez voir que False est affiché.

...
LogPython: False
LogPython: False
LogPython: False
LogPython: False
...

Cela semble bien pour le moment. Éteignez la sortie de la console et passez à la suivante.

Mettre NumPy dans

Lorsque j'ai désinstallé la relation hdf5, j'ai également désinstallé NumPy qui était installé en tant que dépendances, mais après tout je voulais l'utiliser, donc je n'inclurai que NumPy.

$ ./python.exe -m pip install --target . numpy
Successfully installed numpy-1.17.3

Faire progresser la mise en œuvre des contrôles liés à l'action.

Tout d'abord, j'ai défini l'attribution des numéros d'action. Assurez-vous de vérifier les doublons et s'ils sont correctement inclus dans la liste.

Content\Scripts\action.py


import numpy as np
...
ACTION_ROTATE_X_LEFT_1 = 1
ACTION_ROTATE_X_LEFT_2 = 2
ACTION_ROTATE_X_LEFT_3 = 3
ACTION_ROTATE_X_RIGHT_1 = 4
ACTION_ROTATE_X_RIGHT_2 = 5
ACTION_ROTATE_X_RIGHT_3 = 6
ACTION_ROTATE_Y_UP_1 = 7
ACTION_ROTATE_Y_UP_2 = 8
ACTION_ROTATE_Y_UP_3 = 9
ACTION_ROTATE_Y_DOWN_1 = 10
ACTION_ROTATE_Y_DOWN_2 = 11
ACTION_ROTATE_Y_DOWN_3 = 12
ACTION_ROTATE_Z_UP_1 = 13
ACTION_ROTATE_Z_UP_2 = 14
ACTION_ROTATE_Z_UP_3 = 15
ACTION_ROTATE_Z_DOWN_1 = 16
ACTION_ROTATE_Z_DOWN_2 = 17
ACTION_ROTATE_Z_DOWN_3 = 18

ACTION_LIST = [
    ACTION_ROTATE_X_LEFT_1,
    ACTION_ROTATE_X_LEFT_2,
    ACTION_ROTATE_X_LEFT_3,
    ACTION_ROTATE_X_RIGHT_1,
    ACTION_ROTATE_X_RIGHT_2,
    ACTION_ROTATE_X_RIGHT_3,
    ACTION_ROTATE_Y_UP_1,
    ACTION_ROTATE_Y_UP_2,
    ACTION_ROTATE_Y_UP_3,
    ACTION_ROTATE_Y_DOWN_1,
    ACTION_ROTATE_Y_DOWN_2,
    ACTION_ROTATE_Y_DOWN_3,
    ACTION_ROTATE_Z_UP_1,
    ACTION_ROTATE_Z_UP_2,
    ACTION_ROTATE_Z_UP_3,
    ACTION_ROTATE_Z_DOWN_1,
    ACTION_ROTATE_Z_DOWN_2,
    ACTION_ROTATE_Z_DOWN_3,
]
...
def test_ACTION_LIST():
    assert_equal(
        len(ACTION_LIST), len(np.unique(ACTION_LIST))
    )
    members = inspect.getmembers(sys.modules[__name__])
    for obj_name, obj_val in members:
        if not obj_name.startswith('ACTION_ROTATE_'):
            continue
        assert_true(isinstance(obj_val, int))
        is_in = obj_val in ACTION_LIST
        assert_true(is_in)


python_test_runner.run_tests(
    ue=ue,
    target_module=sys.modules[__name__])

Ajoutez la fonction de chaque action à BP_Action en fonction du nom de constante défini. J'ai pensé, mais la fonction pour calculer la liste des cubes cibles par rotation était écrite dans le plan de niveau. J'ai l'impression qu'appeler cette fonction depuis BP_Action semble être un problème ... (je suis confus car je ne peux pas y faire référence tant que je ne m'y suis pas habitué ...) Référence: Considérez comment faire référence au plan de niveau

Je ne peux pas m'empêcher de pleurer, donc je vais d'abord ajouter une fonction au plan de la classe de base du cube pour obtenir la valeur booléenne de savoir s'il s'agit du cube cible à chaque rotation (puis du cube dans le niveau). Peut être pris avec le nœud Get All Actors Of Class ...).

Essayez la bibliothèque de fonctions

Avant de continuer, j'ai écrit quelque chose comme un assistant d'assertion dans le plan de niveau, mais cela peut être gênant car il ne peut pas être utilisé dans la classe BP, donc je vais envisager de l'ajuster.

Quand je l'ai recherché, il semble qu'il y ait une bibliothèque de fonctions.

Qu'est-ce qu'une bibliothèque de fonctions? Un plan qui vous permet d'avoir diverses fonctions accessibles de n'importe où en un seul endroit. Contrairement aux plans normaux, il ne peut pas contenir de variables et il n'y a pas de graphe d'événements. Vous ne pouvez pas créer de macros, uniquement des fonctions. Les fonctions qui y sont écrites seront utilisables quel que soit le plan directeur, l'acteur ou le niveau. [UE4] Bonne utilisation de la bibliothèque de fonctions et de la bibliothèque de macros

Comment le faire a été écrit dans l'article suivant: arc:

Créer une bibliothèque de fonctions UE4 (Blueprint Function Library)

Par convention, quel doit être le nom du dossier de la bibliothèque de fonctions ...? Dans les vidéos et les livres, des coutumes telles que BluePrints pour les plans, BP_ au début des noms de fichiers, Matériaux pour les matériaux, etc. ont été introduites, mais comment était la bibliothèque de fonctions en premier lieu? La bibliothèque de fonctions a-t-elle été introduite ... (il n'est pas bon d'oublier immédiatement si vous ne l'avez pas sortie ...)

Pour le moment, ce n'est pas un travail, alors n'hésitez pas à le nommer le dossier Dossiers. Essayez de préfixer le nom de fichier avec LIB_.

image.png

Il semble que vous puissiez le créer en ajoutant un nouveau fichier dans le navigateur de contenu et en sélectionnant Blueprints → Blueprint Function Library.

Je l'ai nommé LIB_Testing parce que je veux ajouter des relations de test.

image.png

Quand je l'ouvre, ça ressemble à ça. Cela ressemble à un plan avec une structure très simple composée uniquement de fonctions et de variables locales. Déplacez ici la fonction que vous souhaitez déplacer et remplacez-la par celle du niveau BP existant.

Il semble que les fonctions ajoutées à la bibliothèque puissent être appelées à partir du niveau BP ou d'autres classes BP telles quelles sans rien de spécial.

image.png

Cela facilite la vérification des valeurs de lumière dans la classe BP.

Ajouter un traitement pour obtenir la valeur booléenne indiquant s'il s'agit ou non de la cible de rotation de la classe de base du cube

Comme mentionné ci-dessus, nous pourrons obtenir la valeur booléenne de savoir s'il s'agit ou non de la cible de rotation à partir de la classe de base de BP_CubeBase, et la rendre appelable depuis BP_Action.

Puisque nous allons créer une fonction qui renvoie une valeur booléenne comme indiqué ci-dessous dans la plage de 1 à 3 dans XYZ, nous ajouterons 9 fonctions.

image.png

La partie commune est séparée en différentes fonctions. De plus, le tableau du type de position du cube cible dans la rotation de la cible est une constante et a déjà été préparé auparavant, nous allons donc l'utiliser.

image.png

Dans le processus commun, tournez la boucle pour le tableau

image.png

Si la valeur de type de la position du cube actuel correspond à la valeur du tableau d'index dans la boucle actuelle, True est renvoyé et s'il n'y a pas de valeur correspondante même après la fin de la boucle, False est renvoyé.

J'omettrai les détails, mais j'écrirai un test pour confirmer dans une certaine mesure le comportement du processus.

image.png

Maintenant que nous avons 9 rotations prêtes et que le test n'est pas pris, nous pouvons passer à autre chose. Essayons d'ajouter une fonction de rotation à BP_Action. Tout d'abord, nous partirons du simple type de rotation non animée.

Tout d'abord, prenez l'acteur du cube et tournez la boucle.

image.png

Après cela, définissez une branche en fonction de la valeur de vérité de savoir s'il s'agit ou non de la cible de rotation préparée précédemment, et si elle est True, faites-la pivoter.

image.png

Je vais également l'essayer du côté Python.

Content\Scripts\action.py


class Action:

    total_delta_time = 0
    is_rotated = False

    def tick(self, delta_time):
        """
Une fonction qui est exécutée à peu près toutes les images pendant le jeu.

        Parameters
        ----------
        delta_time : float
Secondes écoulées depuis le dernier appel de tick.
        """
        self.total_delta_time += delta_time
        if self.total_delta_time > 5 and not self.is_rotated:
            self.uobject.rotateXLeftImmediately1()
            self.is_rotated = True

Pour le moment, il tournera immédiatement une fois toutes les 5 secondes.

20191110_2.gif

J'ai essayé de le prévisualiser, mais cela semble correct. Nous allons construire un autre traitement de rotation de la même manière, mais avant cela, déplacez la fonction pour tester le résultat de la rotation vers le côté bibliothèque de fonctions afin qu'elle puisse être référencée du côté BP_Action, et remplacez la fonction du côté niveau. Et coupez-le.

Après le déplacement, ajoutez une fonction pour vérifier la valeur après la rotation de la cible à la fin de la fonction ajoutée à BP_Action cette fois, et prévisualisez-la pour vous assurer qu'elle ne soit pas prise dans le contrôle.

image.png

Cela semble bien pour le moment, alors ajoutez une série de rotations dans d'autres directions et appelez-la du côté Python pour vérifier l'opération.

20191112_1.gif

Le processus de rotation immédiate via Python semble être correct. La prochaine fois, je travaillerai sur la connexion du processus de rotation d'animation à Python (l'article s'allonge, je vais donc laisser cet article ici).

Points de préoccupation

Résumé de la page de référence

Recommended Posts

Les débutants veulent créer quelque chose comme un cube rubic avec UE4 et en faire une bibliothèque pour un apprentissage amélioré # 4
Les débutants veulent créer quelque chose comme un cube rubic avec UE4 et en faire une bibliothèque pour un apprentissage amélioré # 5
Les débutants veulent créer quelque chose comme un cube rubic avec UE4 et en faire une bibliothèque pour un apprentissage amélioré # 6
Je veux escalader une montagne avec l'apprentissage par renforcement
[Introduction] Je veux créer un robot Mastodon avec Python! 【Débutants】
Apprentissage amélioré 35 python Développement local, attachez un lien vers myModule et importez-le.
Je veux faire un jeu avec Python
Je veux écrire un élément dans un fichier avec numpy et le vérifier.
Un débutant en apprentissage automatique a essayé de créer un modèle de prédiction de courses de chevaux avec python
J'ai essayé de faire quelque chose comme un chatbot avec le modèle Seq2Seq de TensorFlow
J'ai essayé de créer un environnement d'apprentissage amélioré pour Othello avec Open AI gym
Les débutants en apprentissage automatique essaient de créer un arbre de décision
Comment dessiner de manière interactive un pipeline d'apprentissage automatique avec scikit-learn et l'enregistrer au format HTML
Je veux faire un changeur de voix en utilisant Python et SPTK en référence à un site célèbre
Associez Python Enum à une fonction pour la rendre appelable
Expérimentez pour créer un PDF indépendant pour Kindle avec Python
Je veux faire une macro de clic avec pyautogui (désir)
Pour ceux qui souhaitent démarrer l'apprentissage automatique avec TensorFlow2
Je veux faire une macro de clic avec pyautogui (Outlook)
Bibliothèque pour spécifier un serveur de noms en python et dig
Procédure d'inscription PyPI pour ceux qui veulent faire leurs débuts PyPI
Comment créer une caméra de surveillance (caméra de sécurité) avec Opencv et Python
Créez un thermomètre avec Raspberry Pi et rendez-le visible sur le navigateur Partie 4
J'ai essayé de faire un processus d'exécution périodique avec Selenium et Python
Je veux créer un fichier pip et le refléter dans le menu fixe
Jetez quelque chose dans Kinesis avec python et assurez-vous qu'il est dans
Essayez de faire une stratégie de blackjack en renforçant l'apprentissage ((1) Implémentation du blackjack)
J'ai essayé de faire une étrange citation pour Jojo avec LSTM
[Concept] Stratégie pour analyser les données avec python et viser une baisse après les avantages pour les actionnaires
Est-il possible de se lancer dans une entreprise de pré-cotation et de faire fortune avec des stock-options?
[Salut Py (Partie 1)] Je veux faire quelque chose pour le moment, alors commencez par fixer un objectif.
TF2RL: bibliothèque d'apprentissage améliorée pour TensorFlow2.x
<Pour les débutants> bibliothèque python <Pour l'apprentissage automatique>
2.Faites un arbre de décision à partir de 0 avec Python et comprenez-le (2. Bases du programme Python)
Les débutants en Python ont décidé de créer un bot LINE avec Flask (commentaire approximatif de Flask)
Une collection de conseils pour accélérer l'apprentissage et le raisonnement avec PyTorch
Comment rendre le nom du conteneur accessible dans Docker en tant que sous-domaine
Signifie mémo lorsque vous essayez de faire de l'apprentissage automatique avec 50 images
Créez un arbre de décision à partir de 0 avec Python et comprenez-le (4. Structure des données)
Je souhaite créer une application Web en utilisant React et Python flask
Je pensais que je pouvais créer un bon éditeur gitignore, alors j'ai essayé de faire quelque chose comme MVP pour le moment