Studio GuideWorld SDK Guide
Log In

Interacting with an object

Implement an interaction button that appears when a ZEPETO character approaches an object.


800

STEP 1 : Environment set up

  • You can download the animation and button resources used in the interaction sample and guide from the following link.

📘

ZEPETO Interaction Sample

Zepeto Interaction Module

  • Implement the ZEPETO character creation code in Scene as a default.

📘

Please refer to the following guide. [ZEPETOPlayer]


STEP 2 : Setting the Object

Set the object to interact with the ZEPETO character.


  1. Place the object that the ZEPETO character will interact with.
  2. Create a Hierarchy > Create Empty Object and rename it DockPoint.
  • This is the point the ZEPETO character will interact with. Adjust the position of the object.


  • Check that the transform gizmo toggle button at the top of the Unity Editor is Local and rotate the Z-axis(blue arrow) to the outside of the object.


  • After adding the Collider component, check the isTrigger.
  • Adjust the size of the Collider to match the range where the player can interact with the object.


  1. Create Hierarchy > Create Empty Object as a child of DockPoint and rename it IconPos.


STEP 3 : Setting the UI

  1. Create Hierachy > UI > Canvas as the child of the object that the ZEPETO character will interact with and rename it PrefIconCanvas.
    • Set Render Mode to World Space.
    • Set Width and Height to 1 respectively.
    • Uncheck the Ignore Reversed Graphics option on the Graphic Raycaster component.
600

Example of Canvas Setting


  1. Create Hierachy > UI > Button as a child of PrefIconCanvas.
1923

Example Button Image


  1. Once the setting is complete, make it Prefab and delete the remaining PrefIconCanvas in the hierarchy.
1919

Prefab Configuration Example


STEP 4 : Writing a Script

STEP 4 - 1 : InteractionIcon

  1. Create Project > Create > ZEPETO > TypeScript and rename it InteractionIcon.
  2. Write a sample script like below.
import { ZepetoScriptBehaviour } from 'ZEPETO.Script';
import { Camera, Canvas, Collider, GameObject, Transform, Object } from 'UnityEngine';
import { Button } from 'UnityEngine.UI';
import { UnityEvent } from 'UnityEngine.Events';
import { ZepetoPlayers } from 'ZEPETO.Character.Controller';
 
export default class InteractionIcon extends ZepetoScriptBehaviour {
     
    // Icon
    @Header("[Icon]")
    @SerializeField() private prefIconCanvas: GameObject;
    @SerializeField() private iconPosition: Transform;
     
    // Unity Event    
    @Header("[Unity Event]")
    public OnClickEvent: UnityEvent;
    public OnTriggerEnterEvent: UnityEvent;
    public OnTriggerExitEvent: UnityEvent;
 
    private _button: Button;
    private _canvas: Canvas;   
    private _cachedWorldCamera: Camera;
    private _isIconActive: boolean = false;
    private _isDoneFirstTrig: boolean = false;
     
     
    private Update() {
        if (this._isDoneFirstTrig && this._canvas?.gameObject.activeSelf) {
            this.UpdateIconRotation();
        }
    }
     
    private OnTriggerEnter(coll: Collider) {
        if (coll != ZepetoPlayers.instance.LocalPlayer?.zepetoPlayer?.character.GetComponent<Collider>()) {
            return;
        }
         
        this.ShowIcon();
        this.OnTriggerEnterEvent?.Invoke();
    }
 
    private OnTriggerExit(coll: Collider) {
        if (coll != ZepetoPlayers.instance.LocalPlayer?.zepetoPlayer?.character.GetComponent<Collider>()) {
            return;
        }
         
        this.HideIcon();
        this.OnTriggerExitEvent?.Invoke();
    }
     
    public ShowIcon(){
        if (!this._isDoneFirstTrig) {
            this.CreateIcon();
            this._isDoneFirstTrig = true;
        }
        else {
            this._canvas.gameObject.SetActive(true);
        }
        this._isIconActive = true;
    }
     
    public HideIcon() {
        this._canvas?.gameObject.SetActive(false);
        this._isIconActive = false;
    }
 
    private CreateIcon() {
        if (this._canvas === undefined) {
            const canvas = GameObject.Instantiate(this.prefIconCanvas, this.iconPosition) as GameObject;
            this._canvas = canvas.GetComponent<Canvas>();
            this._button = canvas.GetComponentInChildren<Button>();
            this._canvas.transform.position = this.iconPosition.position;
        }
        this._cachedWorldCamera = Object.FindObjectOfType<Camera>();
        this._canvas.worldCamera = this._cachedWorldCamera;
 
        this._button.onClick.AddListener(() => {
            this.OnClickIcon();
        });
    }
     
    private UpdateIconRotation() {
        this._canvas.transform.LookAt(this._cachedWorldCamera.transform);
    }
 
    private OnClickIcon() {
        this.OnClickEvent?.Invoke();
    }
}
  • The flow of the script is as follows:
    • Update()
      • Call the UpdateIconRotation() custom function to rotate the icon canvas to match the camera rotation.
    • OnTriggerEnter(), OnTriggerExit()
      • When you enter the Collider area and detect a trigger, call the ShowIcon() custom function to activate the icon.
      • When you go out of the collider area, call the HideIcon() custom function to disable the icon.

  1. After completing the script creation, add the script to the DockPoint object.
  2. Assign Pref Icon Canvas, Icon Position from the inspector.

848

Example Script Setting Screen


STEP 4 - 2 : GestureInteraction

  1. Create Project > Create > ZEPETO > TypeScript and rename it to GestureInteraction.
  2. Write a sample script like below.
import { AnimationClip, Animator, HumanBodyBones, Physics, Transform, Vector3, WaitForEndOfFrame} from 'UnityEngine';
import { ZepetoScriptBehaviour } from 'ZEPETO.Script';
import { ZepetoPlayers, ZepetoCharacter } from "ZEPETO.Character.Controller";
 
import InteractionIcon from './InteractionIcon';
 
export default class GestureInteraction extends ZepetoScriptBehaviour {
 
    @SerializeField() private animationClip: AnimationClip;
    @SerializeField() private isSnapBone: boolean = true;
    @SerializeField() private bodyBone: HumanBodyBones;
    @SerializeField() private allowOverlap: boolean = false;
 
    private _interactionIcon: InteractionIcon;
    private _isFirst: boolean = true;
    private _localCharacter: ZepetoCharacter;
    private _outPosition: Vector3;
    private _playerGesturePosition: Vector3;
 
    private Start() {
        this._interactionIcon = this.transform.GetComponent<InteractionIcon>();
        ZepetoPlayers.instance.OnAddedLocalPlayer.AddListener(() => {
            this._localCharacter = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer.character;
        });
 
        this._interactionIcon.OnClickEvent.AddListener(()=> {
            // When onclick interaction icon
            this._interactionIcon.HideIcon();
            this.DoInteraction();
        });
    }
 
    private DoInteraction() {
        this._outPosition = this.transform.position;
 
        if (this.isSnapBone) {
            // Is place empty
            if (this.allowOverlap || this.FindOtherPlayerNum() < 1) {
                this._localCharacter.SetGesture(this.animationClip);
                this.StartCoroutine(this.SnapBone());
                this.StartCoroutine(this.WaitForExit());
            } else {
                // The seats are full.
                this._interactionIcon.ShowIcon();
            }
        } else {
            this._localCharacter.SetGesture(this.animationClip);
            this.StartCoroutine(this.WaitForExit());
        }
    }
 
    private *SnapBone() {
        const animator: Animator = this._localCharacter.ZepetoAnimator;
        const bone: Transform = animator.GetBoneTransform(this.bodyBone);
 
        let idx = 0;
        while(true) {
            const distance = Vector3.op_Subtraction(bone.position, this._localCharacter.transform.position);
            const newPos: Vector3 = Vector3.op_Subtraction(this.transform.position, distance);
 
            this._playerGesturePosition = newPos;
            this._localCharacter.transform.position = this._playerGesturePosition;
            this._localCharacter.transform.rotation = this.transform.rotation;
            yield new WaitForEndOfFrame();
            idx++;
            // Calibrate position during 5 frames of animation.
            if (idx > 5) {
                return;
            }
        }
    }
 
    // The exact method must go through the server code, 
    // but it is calculated by the local client for server optimization.
    private FindOtherPlayerNum() {
        const hitInfos = Physics.OverlapSphere(this.transform.position, 0.1);
 
        let playerNum = 0;
        if (hitInfos.length > 0) {
            hitInfos.forEach((hitInfo) => {
                if (hitInfo.transform.GetComponent<ZepetoCharacter>()) {
                    playerNum ++;
                }
            });
        }
        return playerNum;
    }
 
    private *WaitForExit() {
        if (this._localCharacter) {
            while (true) {
                if (this._localCharacter.tryJump || this._localCharacter.tryMove) {
                    this._localCharacter.CancelGesture();
 
                    this.transform.position = this._outPosition;
                    this._interactionIcon.ShowIcon();
                    break;
                } else if(this.isSnapBone && this._playerGesturePosition != this._localCharacter.transform.position){
                    this._interactionIcon.ShowIcon();
                    break;
                }
                yield;
            }
        }
    }
}
  • The flow of the script is as follows:
    • Start()
      • When the icon is clicked, it deactivates and calls the DoInteraction() custom function.
    • DoInteraction()
      • If isSnapBone is checked,
        • If the seat is empty (allowOverlap is checked, or FindOtherPlayerNum() Custom function return value is less than 1)
          • Take the gesture assigned to the animationClip.
          • Start the SnapBone() Coroutine and attach the bodyBone of the ZEPETO character to the targetTranform.
          • Start WaitForExit() Coroutine.
            • When the ZEPETO character jumps or moves, or goes out of the collider area, cancel the gesture and activate the icon.
        • Activate the icon when the seating capacity is full.
      • If isSnapBone is not checked,
        • Take the gesture assigned to the animationClip.
        • Start WaitForExit() Coroutine.
    • FindOtherPlayerNum()
      • Finds the ZEPETO character that exists in the location of the object where the script is attached, and returns how many there are.
      • You can check if the seat is empty or not.

  1. After completing the script creation, add the script to the DockPoint object.
  2. Assign the Animation Clip, Is Snap Bone, Body Bone, and Allow Overlap in the inspector.
    • Assign the Animation Clip. These are gestures to take with interaction.
    • Checks Is SnapBone. Ensure that the part assigned to the body bone is positioned as a DockPoint.
    • Set Body Bone to Hips. Make sure that the hip is positioned in the DockPoint because it will be a sitting gesture.
    • Allow Overlap allows you to determine whether multiple people can sit in a single seat.

599

Example Script Setting Screen


STEP 5 : Play

The button will appear when the ZEPETO character approaches the object, and disappear when it moves away.

If the gesture you set plays when approaching and interacting with the button, it's a success.

In addition to gestures, various events can be implemented to occur after interaction.

The following is an example of implementing an event that creates an item after an interaction.

📘

Zepeto World Sample - Chapter 3 Interaction Sample

https://github.com/naverz/zepeto-world-sample/tree/main/Assets/Chapter3