[PYTHON] Beginners want to make something like a Rubik's cube with UE4 and make it a library for reinforcement learning # 5

Continuing from the last time, this is an article in which beginners will continue to work on creating a Python library involving UE4 (almost as a memorandum for myself ...).

First: # 1 Last time: # 4

Connecting animation rotation processing with Python

In the previous article, we connected the immediate rotation function with Python, but next we will connect the one with animation.

Let's start with the work on the blueprint side. It's almost the same as the BP of immediate rotation. Almost everything you need is ready, so all you have to do is loop around the cube and flag the rotation for the cube you want to rotate.

image.png

Turn the loop,

image.png

Check if it is a rotation target,

image.png

Set a flag.

Prepare a blueprint function for rotation in one direction, and try calling it from Python with provisional code.

Content\Scripts\action.py


...
class Action:

    total_delta_time = 0
    rotation_count = 0

    def tick(self, delta_time):
        """
A function that is executed approximately every frame during game play.

        Parameters
        ----------
        delta_time : float
Elapsed seconds since the last tick call.
        """
        self.total_delta_time += delta_time
        if self.total_delta_time > 3 and self.rotation_count == 0:
            self.uobject.rotateXLeft1()
            self.rotation_count += 1
            return
        if self.total_delta_time > 6 and self.rotation_count == 1:
            self.uobject.rotateXLeft2()
            self.rotation_count += 1
            return
        if self.total_delta_time > 9 and self.rotation_count == 2:
            self.uobject.rotateXLeft3()
            self.rotation_count += 1
            return
        if self.total_delta_time > 12 and self.rotation_count == 3:
            self.uobject.rotateXRight1()
            self.rotation_count += 1
            return
...

20191114_1.gif

I checked the rotation and it seems that there is no problem, so I will proceed to the next. In addition, depending on the rotation target, the display may be broken or rough, but will this be fixed by packaging for distribution? Or is there something missing ...? (Anti-aliasing)

We will proceed with the correspondence of specifying data from external Python

When actually using it, it is necessary to register the library with PyPI (pip) etc., call those libraries from Jupyter etc. and give the action instruction to the UE4 side.

Therefore, we will prepare a table on which data will be written from the Python side of the library. I thought I would proceed with the module via PyActor, but I found some inconveniences, so I will write it with a general-purpose module under common.

Win64\common\sqlite_utils.py


from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
...
declarative_meta = declarative_base()
...

#The status of the state added to the processing target of the action.
ACTION_INSTRUCTION_STATUS_QUEUED = 1

#The status of the running (animating) state of the action.
ACTION_INSTRUCTION_STATUS_RUNNING = 2

#The status of the processed state of the action.
ACTION_INSTRUCTION_STATUS_ENDED = 3


class ActionInstruction(declarative_meta):
    """
Handles action instruction data from the Python library side
Table model.

    Attributes
    ----------
    id : Column of Integer
Primary key column.
    action : Column of Integer
The specified action. The definition is action.According to the value in py.
    current_status : Column of Integer
The status of the current action. In this module
        ACTION_INSTRUCTION_STATUS_Of a constant with a prefix of
Follow the definition.
    skip_animation : Column of Integer
Specify whether to skip the animation.
        0=Do not skip, 1=It is set to skip.
    """

    id = Column(Integer, primary_key=True)
    action = Column(Integer)
    current_status = Column(Integer)
    skip_animation = Column(Integer)
    __tablename__ = 'action_instruction'


session = None


def create_from_python_db_session():
    """
Session for reading SQLite written from Python library
Generate.

    Returns
    -------
    session : Session
Generated SQLite session.

    Notes
    -----
If the session has already been created, the created instance will be returned.
    """
    global session
    if session is not None:
        return session
    session_start_time_str = file_helper.get_session_start_time_str()
    file_name = 'from_python_%s.sqlite' % session_start_time_str
    session = create_session(
        sqlite_file_name=file_name,
        declarative_meta=declarative_meta)
    return session

For the time being, I will add a test to confirm that a session can be created and one line Insert and Delete pass.

Win64\common\tests\test_sqlite_utils.py


def test_create_from_python_db_session():
    session = sqlite_utils.create_from_python_db_session()
    action_instruction = sqlite_utils.ActionInstruction()
    action_instruction.action = 1
    action_instruction.current_status = \
        sqlite_utils.ACTION_INSTRUCTION_STATUS_QUEUED
    action_instruction.skip_animation = 0
    session.add(instance=action_instruction)
    session.commit()

    action_instruction = session.query(
        sqlite_utils.ActionInstruction
    ).filter_by(
        action=1,
        current_status=sqlite_utils.ACTION_INSTRUCTION_STATUS_QUEUED,
        skip_animation=0).one()
    session.delete(action_instruction)
    session.commit()

Next, on the action.py side, write a process to periodically check the values of the prepared table. Before that, if you process with tick, the process will be executed quite frequently and it will be useless (DB access etc. will be executed frequently), so reduce the execution frequency. Try setting it once in about 0.2 seconds (adjust by watching the situation).

It seems that it can be handled by opening BP_Action and adjusting the Tick Interval (secs). The default is 0, and if it is 0, the execution frequency will be based on the frame rate.

image.png

Try specifying the console output on the Python side and check for the time being.

Content\Scripts\action.py


class Action:

...

    def tick(self, delta_time):
        """
A function that is executed at regular intervals during game play.

        Parameters
        ----------
        delta_time : float
Elapsed seconds since the last tick call.
        """
        ue.log(delta_time)

image.png

It runs every 0.2 seconds. Sounds okay.

Add a process to look at the SQLite data at regular intervals and get one action data if it exists. Make sure that None is returned when another action is rotating or when there is no data for the specified action.

Content\Scripts\action.py


class Action:

    def begin_play(self):
        """A function that is executed at the start of the game.
        """
        self.from_python_db_session = \
            sqlite_utils.create_from_python_db_session()

        python_test_runner.run_pyactor_instance_tests(
            pyactor_class_instance=self)

    def tick(self, delta_time):
        """
A function that is executed at regular intervals during game play.

        Parameters
        ----------
        delta_time : float
Elapsed seconds since the last tick call.
        """
        action, skip_animation = self._get_action_instruction_data()
        if action is None:
            return
        pass

    def _get_action_instruction_data(self):
        """
Acquires the set action information. The state where the next action can be executed
(Rotation animation is finished, etc.) and the next action is specified
If it exists, the value of the first unprocessed action is returned.

        Returns
        ----------
        action : int or None
The type value of the retrieved action. ACTION in this module_
A value with the prefix of is set. When the target does not exist
None is set if the next action cannot be executed.
        skip_animation : bool or None
Setting whether to skip the animation. Target does not exist
None is set if the condition is such that the next action cannot be executed.
        """
        if self.is_any_cube_rotating():
            return None, None
        query_result = self.from_python_db_session.query(
            ActionInstruction
        ).filter_by(
            current_status=sqlite_utils.ACTION_INSTRUCTION_STATUS_QUEUED
        ).order_by(
            ActionInstruction.id.asc()
        ).limit(1)
        for action_instruction in query_result:
            action = int(action_instruction.action)
            skip_animation = int(action_instruction.skip_animation)
            if skip_animation == 0:
                skip_animation = False
            else:
                skip_animation = True
            return action, skip_animation
        return None, None

    def is_any_cube_rotating(self):
        """
Gets the boolean value of whether any cube is spinning.

        Returns
        ----------
        is_rotating : bool
Set to True if any cube is spinning.
        """
        is_rotating = self.uobject.isAnyCubeRotating()[0]
        return is_rotating

    def test__get_action_instruction_data(self):

        #The specified action exists and the animation
        #Check the behavior when not skipping is specified.
        action_instruction = sqlite_utils.ActionInstruction()
        action_instruction.action = ACTION_ROTATE_X_LEFT_2
        action_instruction.current_status = \
            sqlite_utils.ACTION_INSTRUCTION_STATUS_QUEUED
        action_instruction.skip_animation = 0
        self.from_python_db_session.add(instance=action_instruction)
        self.from_python_db_session.commit()
        action, skip_animation = self._get_action_instruction_data()
        assert_equal(
            action,
            ACTION_ROTATE_X_LEFT_2)
        assert_false(skip_animation)
        self.from_python_db_session.delete(action_instruction)
        self.from_python_db_session.commit()

        #Check the behavior when the animation is skipped.
        action_instruction = sqlite_utils.ActionInstruction()
        action_instruction.action = ACTION_ROTATE_X_LEFT_2
        action_instruction.current_status = \
            sqlite_utils.ACTION_INSTRUCTION_STATUS_QUEUED
        action_instruction.skip_animation = 1
        self.from_python_db_session.add(instance=action_instruction)
        self.from_python_db_session.commit()
        _, skip_animation = self._get_action_instruction_data()
        assert_true(skip_animation)
        self.from_python_db_session.delete(action_instruction)
        self.from_python_db_session.commit()

        #Check the behavior when the specified data does not exist.
        action, skip_animation = self._get_action_instruction_data()
        assert_equal(action, None)
        assert_equal(skip_animation, None)

    def test_is_any_cube_rotating(self):
        assert_false(
            self.is_any_cube_rotating())

For the execution of the test of the class specified by PyActor, add a function to execute the test method of the target class separately to the general-purpose module, and execute it at the timing of begin_play (actually, it is normal). It would be nice if the function to be tested flows automatically like pytest in pytest, but this is because the class is instantiated from UE4). Because it is connected to UE4, it is difficult to write all test patterns, so I will write tests as far as I can write quickly.

Win64\common\python_test_runner.py


...
def run_pyactor_instance_tests(pyactor_class_instance):
    """
Set to the instance of Python class specified by PyActor
Perform a set of test functions.

    Parameters
    ----------
    pyactor_class_instance : *
An instance of the Python class specified by the target PyActor.
    """
    print('%Start testing s...' % type(pyactor_class_instance))
    is_packaged_for_distribution = \
        file_helper.get_packaged_for_distribution_bool()
    if is_packaged_for_distribution:
        return
    members = inspect.getmembers(pyactor_class_instance)
    for member_name, member_val in members:
        if not inspect.ismethod(member_val):
            continue
        if not member_name.startswith('test_'):
            continue
        print('%s Target function: %s' % (datetime.now(), member_name))
        pre_dt = datetime.now()
        member_val()
        timedelta = datetime.now() - pre_dt
        print('%s ok. %s seconds' % (datetime.now(), timedelta.total_seconds()))

Now that you can specify the action, I will write the place to call the function on the blueprint side linked to the value of the acquired action.

Content\Scripts\action.py


...
ACTION_KEY_FUNC_NAME_DICT = {
    ACTION_ROTATE_X_LEFT_1: 'rotateXLeft1',
    ACTION_ROTATE_X_LEFT_2: 'rotateXLeft2',
    ACTION_ROTATE_X_LEFT_3: 'rotateXLeft3',
    ACTION_ROTATE_X_RIGHT_1: 'rotateXRight1',
    ACTION_ROTATE_X_RIGHT_2: 'rotateXRight2',
    ACTION_ROTATE_X_RIGHT_3: 'rotateXRight3',
    ACTION_ROTATE_Y_UP_1: 'rotateYUp1',
    ACTION_ROTATE_Y_UP_2: 'rotateYUp2',
    ACTION_ROTATE_Y_UP_3: 'rotateYUp3',
    ACTION_ROTATE_Y_DOWN_1: 'rotateYDown1',
    ACTION_ROTATE_Y_DOWN_2: 'rotateYDown2',
    ACTION_ROTATE_Y_DOWN_3: 'rotateYDown3',
    ACTION_ROTATE_Z_UP_1: 'rotateZUp1',
    ACTION_ROTATE_Z_UP_2: 'rotateZUp2',
    ACTION_ROTATE_Z_UP_3: 'rotateZUp3',
    ACTION_ROTATE_Z_DOWN_1: 'rotateZDown1',
    ACTION_ROTATE_Z_DOWN_2: 'rotateZDown2',
    ACTION_ROTATE_Z_DOWN_3: 'rotateZDown3',
}
...
class Action:

...

    def tick(self, delta_time):
        """
A function that is executed at regular intervals during game play.

        Parameters
        ----------
        delta_time : float
Elapsed seconds since the last tick call.
        """
        action, skip_animation = self._get_action_instruction_data()
        if action is None:
            return
        action_func_name: str = self._get_action_func_name(
            action=action,
            skip_animation=skip_animation,
        )
        action_func = getattr(self.uobject, action_func_name)
        action_func()

    def _get_action_func_name(self, action, skip_animation):
        """
From the type value of the specified action, etc., of the target blueprint
Get the function name.

        Parameters
        ----------
        action : int
The type value of the target action.
        skip_animation : bool
Whether to skip the animation.

        Returns
        ----------
        func_name : str
The calculated function name.
        """
        func_name: str = ACTION_KEY_FUNC_NAME_DICT[action]
        if skip_animation:
            last_char = func_name[-1]
            func_name = func_name[0:-1]
            func_name += 'Immediately%s' % last_char
        return func_name
...
    def test__get_action_func_name(self):
        func_name = self._get_action_func_name(
            action=ACTION_ROTATE_X_LEFT_2,
            skip_animation=False)
        assert_equal(func_name, 'rotateXLeft2')

        func_name = self._get_action_func_name(
            action=ACTION_ROTATE_X_LEFT_2,
            skip_animation=True)
        assert_equal(func_name, 'rotateXLeftImmediately2')

We prepare a dictionary with the function name according to the action type value and refer to it to get the function name. If it is set to skip the animation, it corresponds by adding the suffix of the function name for immediate rotation.

If this is okay, you should be able to rotate on the UE4 side by writing data to the SQLite table. Try adding records to SQLite manually.

image.png

20191116_1.gif

It seems that it rotates as specified by SQLite as expected. I also set the type values of other actions and checked the rotation, but that seems to be no problem.

Adjust the file name of SQLite etc.

I was thinking about whether to separate files according to the writing direction between Python in UE4 and Python in the library, but I started to feel that it was useless because it became necessary to write to each other. Try to combine the files into one, and adjust the file name and variable name (details are omitted).

Make sure to update the status of the rotation status

Currently, the value of current_status of SQLite is not updated, so the rotation is repeated, so adjust that area.

Mainly add the processing of the following three functions.

--In the tick, if the animation is finished, set the end status if there is an action specified during the animation. --In the tick, if the action to be processed is specified and the setting is to animate, set the status during animation. --In the tick, if the action to be processed is specified and the animation is skipped, set the status of immediate termination.

Content\Scripts\action.py


...
    def tick(self, delta_time):
        """
A function that is executed at regular intervals during game play.

        Parameters
        ----------
        delta_time : float
Elapsed seconds since the last tick call.
        """
        self._set_ended_status_to_animation_ended_action()
        action_instruction_id, action, skip_animation = \
            self._get_action_instruction_data()
        if action_instruction_id is None:
            return
        action_func_name: str = self._get_action_func_name(
            action=action,
            skip_animation=skip_animation,
        )
        action_func = getattr(self.uobject, action_func_name)
        action_func()
        self._set_running_status(
            action_instruction_id=action_instruction_id,
            skip_animation=skip_animation)
        self._set_ended_status_if_animation_skipped(
            action_instruction_id=action_instruction_id,
            skip_animation=skip_animation)

    def _set_ended_status_to_animation_ended_action(self):
        """
During animation, if animation is complete
Completed to the setting status of the set action
        (ACTION_INSTRUCTION_STATUS_ENDED) is set.

        Returns
        ----------
        updated : bool
Whether the update process has been executed.
        """
        if self.is_any_cube_rotating():
            return False
        target_data_exists = False
        action_instructions = self.action_data_db_session.query(
            sqlite_utils.ActionInstruction
        ).filter_by(
            current_status=sqlite_utils.ACTION_INSTRUCTION_STATUS_RUNNING,
        )
        action_instruction: sqlite_utils.ActionInstruction
        for action_instruction in action_instructions:
            target_data_exists = True
            action_instruction.current_status = \
                sqlite_utils.ACTION_INSTRUCTION_STATUS_ENDED
        if target_data_exists:
            self.action_data_db_session.commit()
            return True
        return False

    def _set_ended_status_if_animation_skipped(
            self, action_instruction_id, skip_animation):
        """
In case of setting to skip animation, target action setting
Completed to status of (ACTION_INSTRUCTION_STATUS_ENDED)
Set.

        Parameters
        ----------
        action_instruction_id : int
The ID of the primary key of the target action setting.
        skip_animation : bool
Whether to skip the animation.

        Returns
        ----------
        updated : bool
Whether the update process has been executed.
        """
        if not skip_animation:
            return False
        action_instruction: sqlite_utils.ActionInstruction
        action_instruction = self.action_data_db_session.query(
            sqlite_utils.ActionInstruction
        ).filter_by(
            id=action_instruction_id
        ).one()
        action_instruction.current_status = \
            sqlite_utils.ACTION_INSTRUCTION_STATUS_ENDED
        self.action_data_db_session.commit()
        return True

    def _set_running_status(self, action_instruction_id, skip_animation):
        """
If the rotation setting is animated, the target action setting
Running to status (ACTION)_INSTRUCTION_STATUS_RUNNING)
Set.

        Parameters
        ----------
        action_instruction_id : int
The ID of the primary key of the target action setting.
        skip_animation : bool
Whether to skip the animation.

        Returns
        ----------
        updated : bool
Whether the update process has been executed.
        """
        if skip_animation:
            return False
        action_instruction: sqlite_utils.ActionInstruction
        action_instruction = self.action_data_db_session.query(
            sqlite_utils.ActionInstruction
        ).filter_by(
            id=action_instruction_id).one()
        action_instruction.current_status = \
            sqlite_utils.ACTION_INSTRUCTION_STATUS_RUNNING
        self.action_data_db_session.commit()
        return True

I actually put the values in the SQLite table and confirmed that it rotates only once. Also, check that the status value is 2 (during animation) during animation and 3 (finished) when finished, and for immediate rotation processing.

image.png

Now the animation doesn't stop ...

Save the value of Observation

Create values for the "Observation" section of Introduction to Reinforcement Learning # 1 Basic Terms and Introduction to Gym, PyTorch. It's an observation value for learning.

As for what to do

--Add a new SQLite table for Observation --Save the Observation value when the action ends

We will add processing such as.

First, add a model.

Win64\common\sqlite_utils.py


class Observation(declarative_meta):
    """
A model of a table that handles observed data of action results.
The value is saved each time the action ends (animation ends).

    Attributes
    ----------
    id : Column of Integer
Primary key column.
    action_instruction_id : int
The ID of the primary key of the target action information (completed action).
    position_num : int
Cube position number. It is set in order for each surface.
    color_type : int
The color type value of the target position.
    """
    id = Column(Integer, primary_key=True)
    action_instruction_id = Column(Integer, index=True)
    position_num = Column(Integer)
    color_type = Column(Integer)
    __tablename__ = 'observation'

Next, we will add the position type value and the color type definition. The front is orange, and the world standard color scheme is set and assigned as described below and screenshots. The color scheme is matched with the world standard color scheme on Wikipedia.

image.png

[Rubik's Cube-Wikipedia](https://ja.wikipedia.org/wiki/%E3%83%AB%E3%83%BC%E3%83%93%E3%83%83%E3%82%AF% Image quoted from E3% 82% AD% E3% 83% A5% E3% 83% BC% E3% 83% 96).

Define position type value

Orange side (front)

Allocate from 1 to 9 as shown below. We will use the word "FRONT" in the constant name.

temp1119_1.png

White side (left side)

Allocate from 10 to 18 as shown below. We will use the word "LEFT" in the constant name.

temp1119_2.png

Yellow side (right side)

Allocate from 19 to 27 as shown below. We will use the word "RIGHT" in the constant name.

temp1119_3.png

Green surface (top surface)

Allocate between 28 and 36 as shown below. We will use the word "TOP" in the constant name.

temp1119_4.png

Blush (back)

Allocate between 37 and 45 as shown below. We will use the word "BACK" in constant names.

temp1120_1.png

Blue side (bottom)

Allocate between 46 and 54 as shown below. We will use the word "BOTTOM" in the constant name.

temp1120_2.png

Add a position constant

Define it in Python. The number of the constant name is set by the row and column number.

Win64\common\sqlite_utils.py


POSITION_NUM_FRONT_1_1 = 1
POSITION_NUM_FRONT_2_1 = 2
POSITION_NUM_FRONT_3_1 = 3
POSITION_NUM_FRONT_1_2 = 4
POSITION_NUM_FRONT_2_2 = 5
POSITION_NUM_FRONT_3_2 = 6
POSITION_NUM_FRONT_1_3 = 7
POSITION_NUM_FRONT_2_3 = 8
POSITION_NUM_FRONT_3_3 = 9

POSITION_NUM_FRONT_LIST = [
    POSITION_NUM_FRONT_1_1,
    POSITION_NUM_FRONT_2_1,
    POSITION_NUM_FRONT_3_1,
    POSITION_NUM_FRONT_1_2,
    POSITION_NUM_FRONT_2_2,
    POSITION_NUM_FRONT_3_2,
    POSITION_NUM_FRONT_1_3,
    POSITION_NUM_FRONT_2_3,
    POSITION_NUM_FRONT_3_3,
]

POSITION_NUM_LEFT_1_1 = 10
POSITION_NUM_LEFT_2_1 = 11
POSITION_NUM_LEFT_3_1 = 12
...
POSITION_NUM_LIST = [
    POSITION_NUM_FRONT_1_1,
    POSITION_NUM_FRONT_2_1,
    POSITION_NUM_FRONT_3_1,
    POSITION_NUM_FRONT_1_2,
    POSITION_NUM_FRONT_2_2,
    POSITION_NUM_FRONT_3_2,
    POSITION_NUM_FRONT_1_3,
    POSITION_NUM_FRONT_2_3,
    POSITION_NUM_FRONT_3_3,
    POSITION_NUM_LEFT_1_1,
    POSITION_NUM_LEFT_2_1,
    POSITION_NUM_LEFT_3_1,
    POSITION_NUM_LEFT_1_2,
    ...
    POSITION_NUM_BOTTOM_1_3,
    POSITION_NUM_BOTTOM_2_3,
    POSITION_NUM_BOTTOM_3_3,
]

Define a color type value

This is simply defined in 6 colors.

Win64\common\sqlite_utils.py


COLOR_TYPE_ORANGE = 1
COLOR_TYPE_WHITE = 2
COLOR_TYPE_YELLOW = 3
COLOR_TYPE_GREEN = 4
COLORR_TYPE_BLUE = 5
COLOR_TYPE_RED = 6

COLOR_TYPE_LIST = [
    COLOR_TYPE_ORANGE,
    COLOR_TYPE_WHITE,
    COLOR_TYPE_YELLOW,
    COLOR_TYPE_GREEN,
    COLORR_TYPE_BLUE,
    COLOR_TYPE_RED,
]

Connect the reset process to the Python side

Not long ago, the reset process was done directly on the level blueprint, but we'll connect that to Python. Like the other functions before, we have defined each function in the level blueprint, so we will add a function library and move it so that we can refer to it from the level and BP_Action. (From now on, let's deal with it from the beginning with the function library ... (self-advised))

image.png

As for the processing, the contents that were in the blueprint of almost the level are kept as they are, and only a part such as the acquisition process of the cube is adjusted (replacement with the Get All Actors of Class node, etc., only the part that is not brought as it is adjusted).

Try running it once and make sure it is shuffled (at this point you are running the functions in the function library from the level BP).

image.png

There seems to be no part where the assert processing on each BP and the test on Python are stuck, so it seems to be okay.

Add an interface to BP_Action for calling from Python.

image.png

It's a simple function that just calls a function in the function library. We will also adjust Python. Add an action definition.

Content\Scripts\action.py


...
ACTION_ROTATE_Z_DOWN_2 = 17
ACTION_ROTATE_Z_DOWN_3 = 18
ACTION_RESET = 19
...
ACTION_LIST = [
    ...
    ACTION_ROTATE_Z_DOWN_2,
    ACTION_ROTATE_Z_DOWN_3,
    ACTION_RESET,
]
...
ACTION_KEY_FUNC_NAME_DICT = {
    ...
    ACTION_ROTATE_Z_DOWN_2: 'rotateZDown2',
    ACTION_ROTATE_Z_DOWN_3: 'rotateZDown3',
    ACTION_RESET: 'reset',
}

Let's try to input the data of 19 actions directly in SQLite and check the operation.

image.png

image.png

The cube has been shuffled properly. Even if I update the display of the data on the SQLite side and check it, the action is in the completed status.

image.png

I went here quickly. We will continue to move forward.

Think about how to take color values on each side of each cube

You can tell where each cube is located by the implementation so far, but it is necessary to think about which side you can see because rotation etc. are involved (this value is necessary for Observation) Will be).

I thought that it would be calculated from the amount of rotation, but since it is rotated based on the world standard and rotated over and over again, there are many cases where it does not become a beautiful value.

image.png

It seems simple, but there are some signs that the calculation seems to be troublesome ... What should I do ... I thought about it for a while, but it seems that I can calculate the position of the actor (plane of each color) of the plane of the base class of the cube based on the world, so I will try it.

Look up the Blueprint dictionary

I wanted to get the coordinates of the XYZ world reference that the plane is supposed to be based on the type value of each position, but I haven't used such a dictionary or associative array node yet. Let's find out what it looks like.

It seems that the story changes a little between C ++ and Blueprint, but it seems that Map should be used on Blueprint. It seems that not all types can be thrown in, but it seems that they probably support types such as basic numbers and strings.

As far as I know, not every type of variable can be defined as a Map or Set in Blueprints... Maybe there's no such limitation in c++, but I'm not sure. Dictionary?

A multidimensional associative array ... Like a multidimensional array, it doesn't look like you're looking for it? is not it···.

With that in mind, it seems simple to define those multidimensional dictionaries on the Python side, get only the coordinate values of the plane from UE4, and calculate on the Python side.

Check the coordinates of the plane of the world reference at each position

Basically, if the center coordinate is 0 and the plane of the edge (visible color surface) part is 151 (cube size is 100, radius is 50 from the center reference, the color part of the plane is slightly smaller than the cube Since 1 is added so that the color can be seen on the outside, a value such as 151) should come out. We will output to the console and check it in the BeginPlay event of the base class of the cube.

image.png

The above is an example of an orange plane, but the cube name and world-based coordinates (GetWorldLocation) are output (deleted after confirmation). Below, I will make a note in the format of -: (I thought it was okay because I made it based on coordinates 0, but I rotated it in advance. It has been confirmed that the target value does not change after that).

Front plane position

Plane position on the left side

Plane position on the right side

Planar position of the upper surface

Plane position on the back

Planar position of the bottom

Add a process to get a list of coordinates of the current plane for each color on the blueprint side

For convenience of determining the position of colors on the Python side, we will add a process to acquire the world-based coordinate array of the plane for each color in Blueprint. I will connect with Python later.

In order to unify the overlapping parts of the functions as much as possible, we will standardize except for the specification of the target plane. I was wondering how to get the actors in a specific BP class ... but there is a sign that I can get it with a node called Get All Actors with Tag.

image.png

When I searched for where to set the tag, it seems that it can be set normally in the Details window.

image.png

I will try to make what I expected using these. ...., but when I tried it, it seems that I couldn't get a flat actor with this (I don't understand well and worry about 10 minutes ...). The array of the number of returned values will always be 0. Apparently the Get Components by Tag node is a better fit. I'm a little confused, but is it because the acquisition target is a component rather than an actor? Does that mean?

Is the definition of an actor something that is placed at a level such as a blueprint class, or a component that is stored within those actors? If you don't look it up later, it won't look neat ...

image.png

For the time being, if it is a Get Components by Tag node, it seems that the plane has been taken normally, so I will regain my mind and proceed.

A blueprint function library file called LIB_CubeColor has been added to support this.

image.png

The tag name of the plane to be acquired is specified by the argument. The tag seems to use the purple Name type. How is it different from String?

A check is placed to see if the tag name expected at the beginning of the function has an argument so that you can immediately notice if you made a mistake in specifying the tag name.

image.png

If it is not the value of the array of ↑, an error message will be output.

image.png

image.png

Then we are spinning a loop for each cube in the world. Since the Get Components by Tag node for plane acquisition could not be called without going through the cube actor, the cube actor is acquired first. After all, is the actor more suitable with the recognition that the parent / component is the child?

image.png

Get the plane from the cube actor with the Get Components by Tag node. For the Components Class, I chose the Static Mesh Component (because this was the plane display on the BP).

image.png

Checking if the plane is displayed in the Is Visible node. The base class of the cube has a plane of one color in the arrangement itself, and since the display / non-display is set in the BP of the subclass of each cube, the hidden ones are excluded. I will.

image.png

After that, GetWorldLocation takes the coordinates of the plane world reference and adds it to the array for the return value. If it is a float, the value will deviate slightly, and for comparison, it is converted to an integer with a Round node in between.

We will create the processing after the loop is over. For the time being, I will put a process for checking whether the number of result data is 9 properly.

image.png

image.png

Finally, it returns 3 arrays and finishes.

image.png

I'll give it a try. For the time being, let's output the coordinates of the orange plane before animation.

The combination matches the list of coordinates of the front cube that I looked up a while ago. Sounds okay.

It's getting longer, so I'd like to end this article around here. Next time, we will proceed with various implementations around Observation.

Reference page summary

Recommended Posts

Beginners want to make something like a Rubik's cube with UE4 and make it a library for reinforcement learning # 4
Beginners want to make something like a Rubik's cube with UE4 and make it a library for reinforcement learning # 5
Beginners want to make something like a Rubik's cube with UE4 and make it a library for reinforcement learning # 6
I want to climb a mountain with reinforcement learning
[Introduction] I want to make a Mastodon Bot with Python! 【Beginners】
Reinforcement learning 35 python Local development, paste a link to myModule and import it.
I want to make a game with Python
I want to write an element to a file with numpy and check it.
Machine learning beginners tried to make a horse racing prediction model with python
Load a photo and make a handwritten sketch. With zoom function. Tried to make it.
I tried to make something like a chatbot with the Seq2Seq model of TensorFlow
I tried to create a reinforcement learning environment for Othello with Open AI gym
Machine learning beginners try to make a decision tree
How to interactively draw a machine learning pipeline with scikit-learn and save it in HTML
I want to make a voice changer using Python and SPTK with reference to a famous site
Associate Python Enum with a function and make it Callable
I want to make a blog editor with django admin
Experiment to make a self-catering PDF for Kindle with Python
I want to make a click macro with pyautogui (desire)
For those who want to start machine learning with TensorFlow2
I want to make a click macro with pyautogui (outlook)
Library for specifying a name server and dig with python
PyPI registration steps for those who want to make a PyPI debut
Make it possible to output a log to a file with go echo
[For beginners] How to register a library created in Python in PyPI
I tried to make a periodical process with Selenium and Python
I want to create a pipfile and reflect it in docker
Throw something to Kinesis with python and make sure it's in
Try to make a blackjack strategy by reinforcement learning ((1) Implementation of blackjack)
I tried to make a strange quote for Jojo with LSTM
[Concept] A strategy to analyze data with python and aim for a decline after shareholder benefits to make a profit
Is it possible to enter a venture before listing and make a lot of money with stock options?
[Hi Py (Part 1)] I want to make something for the time being, so first set a goal.
TF2RL: Reinforcement learning library for TensorFlow2.x
<For beginners> python library <For machine learning>
2. Make a decision tree from 0 with Python and understand it (2. Python program basics)
Python beginners decided to make a LINE bot with Flask (Flask rough commentary)
A collection of tips for speeding up learning and reasoning with PyTorch
I tried to make a calculator with Tkinter so I will write it
Memorandum of means when you want to make machine learning with 50 images
Make a decision tree from 0 with Python and understand it (4. Data structure)
I want to make a web application using React and Python flask
I thought I could make a nice gitignore editor, so I tried to make something like MVP for the time being