Hey all,
I've created an overworld scene, which I'd like to set up like a Final Fantasy game - with different battle arenas and enemies appearing based on what terrain the player is on when the battle starts.

Now, I've set up the Free Footsteps System - which chooses footstep sounds based on texture maps. I'm sure there is a way to use that existing information to choose a battle zone - I just don't know enough scripting to figure it out. Anyone else encountered this need before, or are talented enough to work it out?
---- 3D animator and artist with (very) basic programming knowledge hoping to make a functional game! ----
  • At the moment I have very crude colliders that somewhat do this job, but I figure this would be more efficient and precise.
    ---- 3D animator and artist with (very) basic programming knowledge hoping to make a functional game! ----
  • In the 2D game i made last year, i used collider and worked fine, i had different battles and battle arena depending on which zone the player started the encounter (like in dungeon i had bats, slimes, ghosts and the background was a catacomb image). What are you trying to achieve in details? I am not good at programming but maybe if it can be reached in ORK i can help you. ^_^
  • My understanding of the code below is tentative at best- I can understand how it works together in the end, but reading the code to understand what variable does what is beyond me. It'd be awesome if you can figure it out, but you don't have to on my account.

    Here is the script that lets you define textures and associated sounds:

    systusing UnityEngine;

    [System.Serializable]
    public struct SurfaceDefinition {
    public string name;
    public AudioClip[] footsteps;
    }

    [System.Serializable]
    public struct RegisteredMaterial {
    public Texture texture;
    public int surfaceIndex;
    }

    public class SurfaceManager : MonoBehaviour {

    public static SurfaceManager singleton;

    [SerializeField] SurfaceDefinition[] definedSurfaces;
    [SerializeField] RegisteredMaterial[] registeredTextures;

    int n;


    void Start() {
    if(!singleton) singleton = this;
    else if(singleton != this) Destroy(gameObject);
    }

    public AudioClip GetFootstep(Collider groundCollider, Vector3 worldPosition) {
    int surfaceIndex = GetSurfaceIndex(groundCollider, worldPosition);

    if(surfaceIndex == -1) {
    return null;
    }

    // Getting the footstep sounds based on surface index.
    AudioClip[] footsteps = definedSurfaces[surfaceIndex].footsteps;
    n = Random.Range(1, footsteps.Length);

    // Move picked sound to index 0 so it's not picked next time.
    AudioClip temp = footsteps[n];
    footsteps[n] = footsteps[0];
    footsteps[0] = temp;

    return temp;
    }

    public string[] GetAllSurfaceNames() {
    string[] names = new string[definedSurfaces.Length];

    for(int i = 0;i < names.Length;i ++) names[i] = definedSurfaces[i].name;

    return names;
    }

    // This is for bullet hit particles
    int GetSurfaceIndex(Ray ray, Collider col, Vector3 worldPos) {
    string textureName = "";

    // Case when the ground is a terrain.
    if(col.GetType() == typeof(TerrainCollider)) {
    Terrain terrain = col.GetComponent<Terrain>();
    TerrainData terrainData = terrain.terrainData;
    float[] textureMix = GetTerrainTextureMix(worldPos, terrainData, terrain.GetPosition());
    int textureIndex = GetTextureIndex(worldPos, textureMix);
    textureName = terrainData.splatPrototypes[textureIndex].texture.name;
    }
    // Case when the ground is a normal mesh.
    else {
    textureName = GetMeshMaterialAtPoint(worldPos, ray);
    }
    // Searching for the found texture / material name in registered materials.
    foreach(var material in registeredTextures) {
    if(material.texture.name == textureName) {
    return material.surfaceIndex;
    }
    }

    return -1;
    }

    // This is for footsteps
    int GetSurfaceIndex(Collider col, Vector3 worldPos) {
    string textureName = "";

    // Case when the ground is a terrain.
    if(col.GetType() == typeof(TerrainCollider)) {
    Terrain terrain = col.GetComponent<Terrain>();
    TerrainData terrainData = terrain.terrainData;
    float[] textureMix = GetTerrainTextureMix(worldPos, terrainData, terrain.GetPosition());
    int textureIndex = GetTextureIndex(worldPos, textureMix);
    textureName = terrainData.splatPrototypes[textureIndex].texture.name;
    }
    // Case when the ground is a normal mesh.
    else {
    textureName = GetMeshMaterialAtPoint(worldPos, new Ray(Vector3.zero, Vector3.zero));
    }
    // Searching for the found texture / material name in registered materials.
    foreach(var material in registeredTextures) {
    if(material.texture.name == textureName) {
    return material.surfaceIndex;
    }
    }

    return -1;
    }

    string GetMeshMaterialAtPoint(Vector3 worldPosition, Ray ray) {
    if(ray.direction == Vector3.zero) {
    ray = new Ray(worldPosition + Vector3.up * 0.01f, Vector3.down);
    }

    RaycastHit hit;

    if (!Physics.Raycast (ray, out hit)) {
    return "";
    }

    Renderer r = hit.collider.GetComponent<Renderer>();
    MeshCollider mc = hit.collider as MeshCollider;

    if (r == null || r.sharedMaterial == null || r.sharedMaterial.mainTexture == null || r == null) {
    return "";
    }
    else if(!mc || mc.convex) {
    return r.material.mainTexture.name;
    }

    int materialIndex = -1;
    Mesh m = mc.sharedMesh;
    int triangleIdx = hit.triangleIndex;
    int lookupIdx1 = m.triangles[triangleIdx * 3];
    int lookupIdx2 = m.triangles[triangleIdx * 3 + 1];
    int lookupIdx3 = m.triangles[triangleIdx * 3 + 2];
    int subMeshesNr = m.subMeshCount;

    for(int i = 0;i < subMeshesNr;i ++) {
    int[] tr = m.GetTriangles(i);

    for(int j = 0;j < tr.Length;j += 3) {
    if (tr[j] == lookupIdx1 && tr[j+1] == lookupIdx2 && tr[j+2] == lookupIdx3) {
    materialIndex = i;

    break;
    }
    }

    if (materialIndex != -1) break;
    }

    string textureName = r.materials[materialIndex].mainTexture.name;

    return textureName;
    }

    float[] GetTerrainTextureMix(Vector3 worldPos, TerrainData terrainData, Vector3 terrainPos) {
    // returns an array containing the relative mix of textures
    // on the main terrain at this world position.

    // The number of values in the array will equal the number
    // of textures added to the terrain.

    // calculate which splat map cell the worldPos falls within (ignoring y)
    int mapX = (int)(((worldPos.x - terrainPos.x) / terrainData.size.x) * terrainData.alphamapWidth);
    int mapZ = (int)(((worldPos.z - terrainPos.z) / terrainData.size.z) * terrainData.alphamapHeight);

    // get the splat data for this cell as a 1x1xN 3d array (where N = number of textures)
    float[,,] splatmapData = terrainData.GetAlphamaps(mapX, mapZ, 1, 1);

    // extract the 3D array data to a 1D array:
    float[] cellMix = new float[splatmapData.GetUpperBound(2) + 1];

    for(int n = 0;n < cellMix.Length;n++) {
    cellMix[n] = splatmapData[0, 0, n];
    }

    return cellMix;
    }

    int GetTextureIndex(Vector3 worldPos, float[] textureMix) {
    // returns the zero-based index of the most dominant texture
    // on the terrain at this world position.
    float maxMix = 0;
    int maxIndex = 0;

    // loop through each mix value and find the maximum
    for(int n = 0;n < textureMix.Length;n ++){
    if (textureMix[n] > maxMix){
    maxIndex = n;
    maxMix = textureMix[n];
    }
    }

    return maxIndex;
    }
    }




    And here is the script that goes on the player to play the sounds based on some colliders placed on the feet:

    // - AUTHOR : Pavel Cristian.
    // - WHERE SHOULD BE ATTACHED : This script should be attached on the main root of the character,
    // on the GameObject the Rigidbody / CharacterController script is attached.
    // - PURPOSE OF THE SCRIPT : The purpose of this script is to gather data from the ground below the character and use the
    // data to find a user-defined sound for the type of ground found.

    // DISCLAIMER : THIS SCRIPT CAN BE USED IN ANY WAY, MENTIONING MY WORK WILL BE GREATLY APPRECIATED BUT NOT REQUIRED.

    using UnityEngine;

    namespace Footsteps {

    public enum TriggeredBy {
    COLLISION_DETECTION, // The footstep sound will be played when the physical foot collides with the ground.
    TRAVELED_DISTANCE // The footstep sound will be played after the character has traveled a certain distance
    }

    public enum ControllerType {
    RIGIDBODY,
    CHARACTER_CONTROLLER
    }

    public class CharacterFootsteps : MonoBehaviour {

    [Tooltip("The method of triggering footsteps.")]
    [SerializeField] TriggeredBy triggeredBy;

    [Tooltip("This is used to determine what distance has to be traveled in order to play the footstep sound.")]
    [SerializeField] float distanceBetweenSteps = 1.8f;

    [Tooltip("To know how much the character moved, a reference to a rigidbody / character controller is needed.")]
    [SerializeField] ControllerType controllerType;
    [SerializeField] Rigidbody characterRigidbody;
    [SerializeField] CharacterController characterController;

    [Tooltip("You need an audio source to play a footstep sound.")]
    [SerializeField] AudioSource audioSource;

    // Random volume between this limits
    [SerializeField] float minVolume = 0.3f;
    [SerializeField] float maxVolume = 0.5f;

    [Tooltip("If this is enabled, you can see how far the script will check for ground, and the radius of the check.")]
    [SerializeField] bool debugMode = true;

    [Tooltip("How high, relative to the character's pivot point the start of the ray is.")]
    [SerializeField] float groundCheckHeight = 0.5f;

    [Tooltip("What is the radius of the ray.")]
    [SerializeField] float groundCheckRadius = 0.5f;

    [Tooltip("How far the ray is casted.")]
    [SerializeField] float groundCheckDistance = 0.3f;

    [Tooltip("What are the layers that should be taken into account when checking for ground.")]
    [SerializeField] LayerMask groundLayers;

    Transform thisTransform;
    RaycastHit currentGroundInfo;
    float stepCycleProgress;
    float lastPlayTime;
    bool previouslyGrounded;
    bool isGrounded;


    void Start() {
    if(groundLayers.value == 0) {
    groundLayers = 1;
    }

    thisTransform = transform;
    string errorMessage = "";

    if(!audioSource) errorMessage = "No audio source assigned in the inspector, footsteps cannot be played";
    else if(triggeredBy == TriggeredBy.TRAVELED_DISTANCE && !characterRigidbody && !characterController) errorMessage = "Please assign a Rigidbody or CharacterController component in the inspector, footsteps cannot be played";
    else if(!FindObjectOfType<SurfaceManager>()) errorMessage = "Please create a Footstep Database, otherwise footsteps cannot be played, you can create a database" +
    " by clicking 'FootstepsCreator' in the main menu";

    if(errorMessage != "") {
    Debug.LogError(errorMessage);
    enabled = false;
    }
    }

    void Update() {
    CheckGround();

    if(triggeredBy == TriggeredBy.TRAVELED_DISTANCE) {
    float speed = (characterController ? characterController.velocity : characterRigidbody.velocity).magnitude;

    if(isGrounded) {
    // Advance the step cycle only if the character is grounded.
    AdvanceStepCycle(speed * Time.deltaTime);
    }
    }
    }

    public void TryPlayFootstep() {
    if(isGrounded) {
    PlayFootstep();
    }
    }

    void PlayLandSound() {
    audioSource.PlayOneShot(SurfaceManager.singleton.GetFootstep(currentGroundInfo.collider, currentGroundInfo.point));
    }

    void AdvanceStepCycle(float increment) {
    stepCycleProgress += increment;

    if(stepCycleProgress > distanceBetweenSteps) {
    stepCycleProgress = 0f;
    PlayFootstep();
    }
    }

    void PlayFootstep() {
    AudioClip randomFootstep = SurfaceManager.singleton.GetFootstep(currentGroundInfo.collider, currentGroundInfo.point);
    float randomVolume = Random.Range(minVolume, maxVolume);

    if(randomFootstep) {
    audioSource.PlayOneShot(randomFootstep, randomVolume);
    }
    }

    void OnDrawGizmos() {
    if(debugMode) {
    Gizmos.DrawWireSphere(transform.position + Vector3.up * groundCheckHeight, groundCheckRadius);
    Gizmos.color = Color.red;
    Gizmos.DrawRay(transform.position + Vector3.up * groundCheckHeight, Vector3.down * (groundCheckDistance + groundCheckRadius));
    }
    }

    void CheckGround() {
    previouslyGrounded = isGrounded;
    Ray ray = new Ray(thisTransform.position + Vector3.up * groundCheckHeight, Vector3.down);

    if(Physics.SphereCast(ray, groundCheckRadius, out currentGroundInfo, groundCheckDistance, groundLayers, QueryTriggerInteraction.Ignore)) {
    isGrounded = true;
    }
    else {
    isGrounded = false;
    }

    if(!previouslyGrounded && isGrounded) {
    PlayLandSound();
    }
    // print(isGrounded);
    }
    }
    }

    ---- 3D animator and artist with (very) basic programming knowledge hoping to make a functional game! ----
  • edited December 2016
    My current messy solution :P

    image
    I suppose I could create custom meshes to make it more precise. So not a huge deal.
    Post edited by Natnie on
    ---- 3D animator and artist with (very) basic programming knowledge hoping to make a functional game! ----
  • It is actually how i did it, do you want to achieve that by script? In addition my battle start event had a variable checker/changer depending on zone, and player group was teleported to different battle scene depending on the variable.
    Maybe you could set an ORK variable in your step-sound script, and get that variable in battle start event, changing the scene accordingly.
  • There are different ways to do this - ORK has none built-in.
    You could e.g. use Makinom (which has nodes to check the used texture) and set ORK variables according to the texture, using the variables to determine the battle.
  • I didn't remember in Makinom there was such an option for checking texture... amazing!
    Is there an event too change sound/animation accordingly? In that case he could just use Makinon+Ork without external script. ^^
Sign In or Register to comment.