Google PayでVISAが使えない問題を解決した話(OPPO Reno 5A)

概要

Google PayでVISAの決済だけうまくいかなかったのですが、Google Play開発者サービスをインストールし直すことで解決しました。その過程で起こった問題などを、備忘録として残しておきます。
使用していたスマホOPPO Reno 5Aです。
記事にすると思っていなかったので、スクリーンショットなどは少ないですが、ご容赦ください。

起こっていた問題

Googleウォレットアプリでの支払い、すなわちGoogle Payでの支払いにおいて、VISA(三井住友カード)の決済だけがうまくいかない問題に遭遇しました。
QUICPaySuicaなどは使用できていましたが、VISAのみ、決済時にエラーが発生していました。
ウォレット(Googleアカウント)から、決済できないVISAを削除して入れなおしてもダメでした。

解決法

調べてみると、Google Play開発者サービスの初期化で直ったという人がいました。 そのため、設定のアプリの項目から「Google Play開発者サービス」のキャッシュとデータを一度削除しました。
しかし動作せず、むしろ、Googleウォレットアプリの表示がおかしくなり、画像の画面から移動できなくなりました。

Google Play開発者サービスのデータを消したらこの画面から動けなくなった

その後、Google Play開発者サービスを再インストールすることを考えました。 しかし、Google Play開発者サービスは設定からアンインストールはできず、Google Playストアで検索してもストアページが表示されませんでした。 ですが、なぜかWebブラウザ経由でならストアページに飛ぶことができました。 play.google.com

ストアページに移動すると、なぜか「アンインストール中」と表示されており、インストールもアンインストールもできない状態でした。 また、自分はベータテストに参加していたので、ベータを解除したり再登録してみても、この表示は変わりませんでした。

そこから、Google Playストアをリセットするため、設定のアプリの項目から「Google Playストア」のキャッシュとデータを削除しました。 その後、再びGoogle Play開発者サービスのストアページに飛ぶことで、インストールし直すことができました。 これによって、Google PayでのVISA決済が正常に行えるようになりました。

まとめ

Google PayのVISA決済がうまくいかない問題は、Google Play開発者サービスをインストールし直すことで直りました。 そのために、 1. ブラウザ経由でGoogle Play開発者サービスのGoogle Playストアページに移動 2. 1.で表示がおかしければ、Google Playストアのキャッシュとデータを削除した後、Google Play開発者サービスを再インストール 3. ストアページで、Google Play開発者サービスをインストールまたは再インストール という手順が必要でした。

ただし、私の場合はこれで解決した、というだけですので、同様の症状に遭遇した人は、あくまで自己責任でお試しください。

SpresenseでI2C通信 (Qwiic接続基板/M5StickC Joystick Hat)

導入

Spresenseを購入したのですが、エッジAIとしてはまだうまく使いこなせておらず…
ひとまず、M5Stackシリーズで使っていたI2Cデバイスを使ってみるところから始めてみました。
また、Spresense用のQuiic接続用の基盤も購入したので、そちらも使用してみました。

使用したもの

・Spresenseメインボード
・Spresense拡張ボード

SonyのAI推論も可能な高性能ボードコンピュータです。基本はArduinoと同じように使用可能です。
拡張ボードをつけることで、ArduinoUnoと同様のピン配置で使用することができます。
(今回やる内容では、拡張ボードは不要ですが、取り外すのが手間だったのでつけたまま使用しています。)
www.sony-semicon.com

・M5StickC Joystick Hat
I2C通信で、X,Y軸の変位(-127 - 127)と押し込み(0 or 1)を取得できるジョイスティックです。
docs.m5stack.com

・SPRESENSE用Qwiic接続基板
SpresenseのI2C通信に、Qwiic端子を使用するための基盤です。
SPRESENSE用Qwiic接続基板www.switch-science.com

配線

I2Cなので、4本のケーブルを繋ぐだけです。
まずは、Quiic接続基盤なしで、Spresenseのメインボードに直接ピンを繋いでみました。
Joystick Hatは、M5StickCに接続するためにピンが8本ありますが、実際に使用するのは4本だけです。

Joystick Hat Spresense
GND GND
VCC 3.3V
SDA D14
SCL D15

https://www.sony-semicon.com/ja/products/spresense/index.html

以下のように接続しました。
黄色がGND、紫色が3.3V、緑色がSCL、青色がSDAです。

Spresenseのピンへ直接接続

Qwiic接続基盤を用いて、Qwiic-4ピン(メス)ケーブルで接続すると、以下のようになります。
黒色がGND、赤色が3.3V、黄色がSCL、青色がSDAです。

Qwiic接続基盤を用いて接続

実践

以下のようなコードで、Joystick Hatの値を読み取って、シリアル通信でデータを表示するようにしました。

動作することが確認できました!

シリアルモニタで動作を確認

【Unity】放物線状にRaycastしたい!

導入

VRゲームをプレイしていると、テレポート先やジャンプ先を決めるときなどに、図のようなUIをよく見かけます。手から放物線のような曲線を放ち、それがヒットした位置に移動する、というものです。

VRでテレポート先を決めるUI

高い位置にある床面を選択することができたり、過度に遠すぎる地点は選択できないなどの特徴があり、実に素晴らしいUIだと思います。 しかし、弧を描くように判定を飛ばして、その衝突位置を取得する、という機能は、Unityには標準ではないように思います。 最も近いのは、直線的にRayを飛ばして、衝突地点の情報を得るPhysics.Raycast()ですよね。 そこで、放物線をサンプリングした点列をもとに、手前から順に短くRaycast()を繰り返すことで、放物線状にRaycast()を行ってみました。

曲線を線分で近似してRaycastを繰り返す
その記録とコードを紹介します。

実装

放物線状のRaycastを行うために、以下のCurveRaycasterクラスを作成しました。

using UnityEngine;

public class CurveRaycaster : MonoBehaviour
{
    //引数のVector3配列の手前から2点ずつ取り出し、Raycastを繰り返し、ヒットしたかどうかを返す関数
    //ヒットした場合、ヒット時の始点のindexをhitStartIndex、ヒット情報をhitで返す
    //ヒットしなかった場合、hitStartIndexに-1を入れ、hitには適当なものが入るので注意
    public static bool ContinuesRaycast(Vector3[] points, out int hitStartIndex, out RaycastHit hit)
    {
        for (int i = 0; i < points.Length - 1; i++)
        {
            Ray ray = new Ray(points[i], points[i + 1] - points[i]);

            if (Physics.Raycast(ray, out hit, Vector3.Distance(points[i], points[i+1])))
            {
                //hit位置までの線を1フレーム表示(エディタ上のみ)
#if UNITY_EDITOR
                Debug.DrawLine(points[i], hit.point, Color.red, Time.deltaTime);
#endif
                hitStartIndex = i;
                return true;
            }
            else
            {
                //2点間を結ぶ線を1フレーム表示(エディタ上のみ)
#if UNITY_EDITOR
                Debug.DrawLine(points[i], points[i+1], Color.red, Time.deltaTime);
#endif
            }
        }
        hitStartIndex = -1;
        //hitには適当なものを入れる(よくない?)
        hit = new RaycastHit();
        return false;
    }

    //初期位置と初速度と時刻から、放物線上の点を求める。
    public static Vector3 CalculateParabolaPosByTime(Vector3 initialPosition, Vector3 initialVelocity, float t)
    {
        Vector3 gravity = Physics.gravity;

        //t秒後の座標
        Vector3 pos_t = initialPosition + initialVelocity * t + (gravity * t * t) / 2.0f;

        return pos_t;
    }

    //初期位置、初期速度から求まる放物線上の、interval秒ごとの位置をnum個並べた座標列を返す
    public static Vector3[] CalculateParabolaPosArray(Vector3 initialPosition, Vector3 initialVelocity, int num, float interval)
    {
        Vector3[] posArray = new Vector3[num];
        for (int i = 0; i < num; i++)
        {
            posArray[i] = CalculateParabolaPosByTime(initialPosition, initialVelocity, i * interval);
        }
        return posArray;
    }
}

重要なのはContinuesRaycast()ですね。Vector3配列を引数で受け取り、「配列の頭から2点ずつ取り出し、その2点間でRaycastを行う」を繰り返し行います、 最初にhitした時点で、trueを返し、そのインデックスとhit情報をoutパラメータで返します。 今回は放物線を用いますが、任意の点列を入力にできます。

そして、放物線上の点を求めるCalculateParabolaPosByTime()と、その点列を取得するCalculateParabolaPosArray()を実装しました。

動作確認

今回は動作していることが分かりやすいように、図のような矢印状のオブジェクトを、ヒット地点に表示するようにしてみました。

使用したUIオブジェクト
動作確認用に作成したスクリプトは以下です。

using UnityEngine;

public class Test : MonoBehaviour
{
    [SerializeField] float initialSpeed;
    float interval = 0.05f;
    int num = 100;
    int lastHitStartIndex = -1;
    RaycastHit lastHit;

    //ヒット地点に表示したいUIオブジェクト
    [SerializeField] GameObject arrowUI;

    void Update()
    {
        //放物線の点列を生成
        Vector3[] points = CurveRaycaster.CalculateParabolaPosArray(transform.position, transform.forward * initialSpeed, num, interval);
        //生成した点列についてContinuesRaycast()
        if(CurveRaycaster.ContinuesRaycast(points, out lastHitStartIndex, out lastHit))
        {
            Debug.Log($"index:{lastHitStartIndex}, hit:{lastHit.collider.name}");

            //UIオブジェクトをアクティブにして、ヒット位置に移動させ、ヒット位置の法線方向を向かせる
            arrowUI.SetActive(true);
            arrowUI.transform.position = lastHit.point;
            arrowUI.transform.rotation = Quaternion.LookRotation(lastHit.normal);
        }
        else
        {
            //ヒットしてないときはUIを非アクティブに
            arrowUI.SetActive(false);
        }
    }
}

以下のように、正しく動作していますね。

動作確認

最後に

今回作成した内容では、
・LayerMaskを指定できない
・LineRendererではなくDebug.DrawRayを使っているので、Game内では線が見えない
という問題があるので、実際にゲームに実装する場合は、もう少し修正が必要ですね。 また、Spline機能をうまく使えたら良かったのですが、今回は考慮していません。 Spline側に、Vector3[]に変換する機能があるだろうと思うので、組み合わせることは可能だとは思いますが…

あまりこの内容を書いてる人がいなかったように思ったので執筆しました。
参考になれば幸いです。

【M5Stack】I2CでLCD(QAPASS 1602A)に文字表示

導入

電子工作を全然やったことがなかった頃に友人にもらった液晶ディスプレイがあったので、動作確認してみた記録です。 PlatformIOでLiquidCrystal_I2Cライブラリを使って関数を呼び出すだけなので、全然難しいことはしていません。
今回使ったマイコンは、M5Stack Basicで、PlatformIOでプログラムを作成しています。

使用したLCD

使用したのは、こちらの写真のもので、基盤を見たところ、QAPASS 1602Aと書いてありました。本体は16ピンですが、裏面にI2Cで制御できるアダプター(?)がついています。今回は、このI2Cアダプター経由で使用します。
画面には16×2文字の表示が可能です。

配線

4本の配線をジャンパワイヤでつなげているだけです。端子の対応は以下のようになります。

LCD M5Stack
GND G
VCC 5V
SDA 21
SCL 22

M5Unified

記事の本題とはずれますが、今回、私は初めてM5Unifiedライブラリを使用しました。M5Stackシリーズには様々な製品があり、製品によって、使用するライブラリや関数が異なります。それらのM5Stackシリーズの異なるハードウェアに共通して使用できるように公開されているのがM5Unifiedです。個別のライブラリを使用するより、こちらを使用する方が便利そうですね。 (私が持っているM5Stackシリーズには対応していないものも多いのですが…) 詳細は以下のサイトをご覧ください。 docs.m5stack.com

実践

ライブラリを使用するために、platform.ini内に以下のように記述しておきます。

[env:m5stack-core-esp32]
platform = espressif32
board = m5stack-core-esp32
framework = arduino
lib_deps = M5Unified
    LiquidCrystal_I2C

本体のコードは以下です。

#include <M5Unified.h>
#include <LiquidCrystal_I2C.h>

//I2CアドレスとLCDの画面サイズを指定
LiquidCrystal_I2C lcd(0x27,16,2);

void setup() {
  //M5Unifiedで本体初期化
  auto cfg = M5.config();
  cfg.serial_baudrate = 9600;
  M5.begin(cfg);
  //本体ディスプレイにHelloWorld!を表示
  M5.Display.setTextSize(3);
  M5.Display.println("HelloWorld!");

  //LCD初期化
  lcd.init();
  //呼び出すことでバックライトON 
  lcd.backlight();
  //左上からHello, world!を表示
  lcd.setCursor(0, 0);
  lcd.print("Hello, world!");
}

void loop(){

}

コードを書き込み、問題なく表示できました!

参考にした記事

iot.keicode.com

【Python・scikit-learn】パーセプトロンで2値分類

導入

今回は、入力データを複数のクラスに分類するクラス分類を行います。
特に、最も簡単な2クラスの分類(2値分類)を、パーセプトロンで実装してみましょう。

クラス分類

クラス分類は、入力データを分類する処理のことを言います。
例えば有名なのは、衣服の写真を入力し、その服がシャツかズボンか、、、などの分類ですね。 この「シャツ」「ズボン」といった分類対象のことをクラスと言います。
(衣服の分類は、Fashion-MNISTという有名なサンプルデータがあるため、よく題材にされています。 ) クラス分類の中でも特に、「この画像は犬かどうか」のような、Yes/Noで出力されるような2クラスの分類を、2値分類と言います。

パーセプトロン

パーセプトロンは、最もシンプルなニューラルネットワークと言えます。ニューラルネットワークについての詳細な解説は省きます。
パーセプトロンは、活性化関数などを持たない単純なニューラルネットワークで、線形な分離しか行えないという特徴があります。

実践

まずは、以下のように、scikit-learnの機能で2値分類用のデータを生成しましょう。

import matplotlib.pyplot as plt
import numpy as np

from sklearn.datasets import make_classification

# 乱数のシードを設定
np.random.seed(3)

#2クラスの分類(2値分類)データを作成
#xには2次元座標、yにはクラスラベル(0か1)が格納される
x,y = make_classification(n_samples=100, 
                          n_features=2, 
                          n_redundant=0, 
                          n_informative=2, 
                          n_clusters_per_class=1, 
                          n_classes=2)

#クラスラベルが0の点を青、クラスラベルが1の点を赤でプロット
plt.scatter(x[y == 0][:, 0], x[y == 0][:, 1], c='blue', label='Class 0')
plt.scatter(x[y == 1][:, 0], x[y == 1][:, 1], c='red', label='Class 1')
plt.legend()
plt.show()

実行結果は以下のようになります。このデータに対して、パーセプトロンで2値分類を行います。

今回分類するデータ

続いて、パーセプトロンの作成と学習です。

from sklearn.linear_model import  Perceptron

#パーセプトロンのインスタンスを生成
perceptron = Perceptron()
#データに対してパーセプトロンをフィッティング
perceptron.fit(x, y)

#学習後の係数と切片を取得
coef = perceptron.coef_[0]
intercept = perceptron.intercept_

#データをプロット
plt.scatter(x[y == 0][:, 0], x[y == 0][:, 1], c='blue', label='Class 0')
plt.scatter(x[y == 1][:, 0], x[y == 1][:, 1], c='red', label='Class 1')

#学習後の境界をプロット
line = np.linspace(-15,15)
plt.plot(line, -(line * coef[0] + intercept) / coef[1], c="g", label="perceptron")
plt.xlim(-2, 4.5)
plt.ylim(-3, 2.5)
plt.legend()

実行結果は以下のようになります。

パーセプトロンによる2値分類の視覚化

このように境界を定めることで、今後新たなデータが得られたときに、そのデータが境界のどちら側にあるかによって、クラスを分類(予測)することができるようになります。
例えば、点(3, 1)という点がどちらのクラスに属するかを予測するには、以下のようなコードで予測できます。

perceptron.predict([[3, 1]])

predict()は、引数のデータが分類されるクラスを返します。 今回の2値分類モデルでは[0]または[1]が返ってきます。点(3,1)に対しては、[0]が結果として返ってきました。

predict()の実行結果

【Python】scikit-learnで非線形回帰(多項式回帰)

導入

先日、直線の線形回帰を行う記事を書きました。
今回は、前の記事で行った線形回帰の基底を変更することで、非線形な回帰を行います。 hotaru-conny.hatenablog.com

線形回帰と多項式回帰

前回行った線形回帰は、xyの学習データをもとに、以下の式のa_1a_0を求める回帰でした。

 \displaystyle
y = a_1 x + a_0\tag{1}

前回の線形回帰では、xyの関係を直線の式で仮定して、そのパラメータを求めたわけです。
となれば、直線以外の式を仮定して回帰を行うことも可能です。シンプルな例でいえば、以下の式で多項式を仮定すると多項式回帰を行うことができます。

 \displaystyle
y = a_n x^n + a_{n-1} x^{n-1} + \cdots + a_1 x + a_0\tag{2}

この多項式回帰では、nが2以上であれば、直線的でない非線形な回帰を行うことができます。

scikit-learnのLinearRegression()

scikit-learnにおけるLinearRegression()は、関数名こそ線形回帰っぽい名前ですが、直線の回帰だけでなく、基底に対する重み付き和に対する回帰として使えます。

前回行った回帰は、1入力1出力で、以下の式のパラメータを求めるものでした。

 \displaystyle
y = a_1 x + a_0\tag{1}

これに対し、多入力1出力の線形回帰は、以下のような式になります。

 \displaystyle
y = a_n x_n + a_{n-1} x_{n-1}  + \cdots+ a_1 x_1 + a_0\tag{3}

scikit-learnのLinearRegression()は、以下のように機能するわけです。

多変数の線形回帰

ここで、多変数に対する線形回帰

 \displaystyle
y = a_n x_n + a_{n-1} x_{n-1}  + \cdots+ a_1 x_1 + a_0\tag{3}

と、多項式回帰

 \displaystyle
y = a_n x^n + a_{n-1} x^{n-1} + \cdots + a_1 x + a_0\tag{2}

は、式の形が似ていますよね。実際、式(3)の x_i x^{i}に置き換えることで、式(2)を得ることができます。したがって、scikit-learnの上では、{x^{n}, x^{n-1}, \cdots, x, 1}を基底としてLinearRegression()を使用することで、多項式回帰を行うことができます。

具体例を図で示しておきましょう。x_1, x_2の2つの入力からyを推論する線形回帰と同様に、1つの入力xをもとに、x^{2}, xを仮想的に2つの入力とみなして線形回帰を行うことで、2次元の多項式回帰を実現します。

多項式基底に対する線形回帰

多項式回帰の実践

実際に多項式回帰を行っていきます。
今回は、事前に作った多項式に確率的な誤差を乗せたデータを作成して、そのデータに対して回帰を行います。

#scikit-learnで多項式回帰
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
import matplotlib.pyplot as plt
import numpy as np

#データ数
num_points = 100
#係数
a2 = 3.5
a1 = 2.5
a0 = 1.0
#誤差の平均と標準偏差
mean = 0
std = 20
#データを作成
x = np.random.uniform(-10, 10, num_points)
y = a2 * x ** 2 + a1 * x + a0 + np.random.normal(mean, std, num_points)

x = x.reshape(-1, 1)
y = y.reshape(-1, 1)

#線形回帰モデルを使う
model = LinearRegression()

#2次元の多項式基底を作成
quadratic = PolynomialFeatures(degree=2)

#xに多項式特徴量を適用
x_poly = quadratic.fit_transform(x)

#モデルにフィッティング
model.fit(x_poly, y)

#プロット用のデータを生成して予測
x_plot = np.linspace(-10, 10, 100).reshape(-1, 1)
x_plot_poly = quadratic.transform(x_plot)
y_plot = model.predict(x_plot_poly)
#データをプロット
plt.scatter(x, y)

#回帰直線をプロット
plt.plot(x_plot, y_plot, color="red")

実行結果は以下になります。

多項式回帰の実行結果

fit_transform()とtransform()

ここで、学習時のxのデータの処理には、quadratic.fit_transform(x)を使っているのに対し、推論時のデータの処理には、quadratic.transform(x)を使っています。
fit_transform()は、引数のデータについて、最大値や平均といった統計データを計算して、どのような前処理を行うか決定し、変換を行う関数です。この時決定された前処理の内容は、メモリに保存されています。
transform()は、メモリに保存されている前処理内容を、単に適用する関数です。
すなわち、学習用データに対しては、前処理の方法を決定&変換しているのに対し、テスト用データに対しては、学習用データに対して行ったのと同じ変換を行っています。

多項式以外の非線形回帰

式(2)の基底をx^{n}, x^{n-1}, \cdots, x, 1に変更することで、多項式回帰を行うことができました。なら、基底をさらに別のもの、例えばe^{x}, x, 1なんかにしても回帰ができると考えられますね。試しにやってみましょう。

import numpy as np
from sklearn.linear_model import LinearRegression
import matplotlib.pyplot as plt

#データセット生成
#データ数
num_points = 100
#係数
a2 = 3.5
a1 = -3000
a0 = 1.0
#誤差の平均と標準偏差
mean = 0
std = 5000
x = np.linspace(0, 10, num_points)
y = a2 * np.exp(x) + a1 * x + a0 + np.random.normal(mean, std, num_points)
x = x.reshape(-1, 1)
y = y.reshape(-1, 1)

#基底を自分で設定
base = np.hstack([np.exp(x), x, np.ones_like(x)])

#モデルの学習
model = LinearRegression()
model.fit(base, y)

#予測
y_pred = model.predict(base)

#プロット
plt.scatter(x, y)
plt.plot(x, y_pred, color='red')
plt.show()

実行結果は以下になります。

指数関数を含む基底に対するLinearRegression()実行結果

対象データは、y=3.5e^{x}-3000x+1です。xが小さい部分では-3000xの項が支配的で右下がりに、xが大きい部分では3.5e^{x}成分が支配的になって右肩上がりになっています。また、回帰の結果でもこのデータを適切に推論できていることが確認できますね。

更新

2024/2/19 説明内容を少し修正。コード中のデータを分かりやすく変更。

【Unity】オブジェクトを複製するエディタ拡張を作ろう!

概要

Unityで、ゲーム内オブジェクトに付与するスクリプトでは、MonoBehaviourを継承するのが普通です。
本記事では、EditorWindowを継承して、簡単なエディタ拡張を作成してみます。

さっそくコーディング

作成したコードは以下のようになります。
EditorWindowクラスを継承し、OnGUI()の中に、Windowとして表示したい内容を書いていますね。

using UnityEditor;
using UnityEngine;

public class ObjectDuplicator : EditorWindow
{
    private GameObject originalObject;
    private int numberOfCopies = 1;
    private Vector3 offset = Vector3.zero;

    //Toolsから呼び出す
    [MenuItem("Tools/ObjectDuplicator")]
    public static void ShowWindow()
    {
        GetWindow<ObjectDuplicator>("ObjectDuplicatorWindow");
    }

    //Windowに表示する内容
    private void OnGUI()
    {
        GUILayout.Label("Let's Duplicate!", EditorStyles.boldLabel);

        //コピー元オブジェクト、コピー数、オフセットをフィールドで受け取る
        originalObject = EditorGUILayout.ObjectField("Original Object", originalObject, typeof(GameObject), true) as GameObject;
        numberOfCopies = EditorGUILayout.IntField("Number of copies", numberOfCopies);
        offset = EditorGUILayout.Vector3Field("offset", offset);

        //ボタンを押したら複製
        if (GUILayout.Button("Duplicate!"))
        {
            DuplicateObjects();
        }
    }

    private void DuplicateObjects()
    {
        //複製対象がnullなら
        if (originalObject == null)
        {
            EditorUtility.DisplayDialog("ObjectDuplicator", "Original Objectが未設定です!", "OK");
            return;
        }
        
        //コピー数が0以下なら
        if (numberOfCopies <= 0)
        {
            EditorUtility.DisplayDialog("ObjectDuplicator", "コピー数が0以下です!", "OK");
            return;
        }

        //複製
        for (int i = 0; i < numberOfCopies; i++)
        {
            GameObject newObject = Instantiate(originalObject);
            newObject.transform.position = originalObject.transform.position + offset * (i+1);
            newObject.name = originalObject.name + "_Copy" + (i + 1);

            //Undoで取り消せるように登録
            Undo.RegisterCreatedObjectUndo(newObject, "Duplicate Object");
        }

        Debug.Log("Duplicated!");
    }
}

使ってみる

実際に使ってみましょう。
上で示したスクリプトは、「Editor」という名前のフォルダ内に配置する必要があるので、気を付けてください。
Toolsタブから呼び出すと、Windowが表示されます。

Toolsタブから呼び出し
Windowが表示される
試しに、Cubeをシーンに配置して、5個複製してみます。
オフセットは、X方向に1.5とします。
Windowにパラメータを設定
複製ボタンを押すと、以下のようになります!
実行後画面
指定したオフセット分ズレながら、複製されていますね!