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 ...).
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.
Turn the loop,
Check if it is a rotation target,
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
...
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)
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.
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)
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.
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.
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).
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.
Now the animation doesn't stop ...
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.
[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).
Allocate from 1 to 9 as shown below. We will use the word "FRONT" in the constant name.
Allocate from 10 to 18 as shown below. We will use the word "LEFT" in the constant name.
Allocate from 19 to 27 as shown below. We will use the word "RIGHT" in the constant name.
Allocate between 28 and 36 as shown below. We will use the word "TOP" in the constant name.
Allocate between 37 and 45 as shown below. We will use the word "BACK" in constant names.
Allocate between 46 and 54 as shown below. We will use the word "BOTTOM" in the constant name.
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,
]
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,
]
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))
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).
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.
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.
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.
I went here quickly. We will continue to move forward.
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.
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.
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.
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.
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
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.
When I searched for where to set the tag, it seems that it can be set normally in the Details window.
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 ...
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.
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.
If it is not the value of the array of ↑, an error message will be output.
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?
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).
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.
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.
Finally, it returns 3 arrays and finishes.
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.
Recommended Posts