Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 12 Next capture
2021 2022 2023
1 capture
12 Jun 22 - 12 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
  • Help Room /
avatar image
0
Question by Lorelock · Nov 10, 2015 at 03:38 PM · 3dmobilepathfindingisometric

Using stairs in 3D grid-based pathfinding (A*)?

Hey guys,

I've been looking at pathfinding in Unity for the past few days and each prototype I have has come up short because I can't have a way of using stairs or ramps to increase/decrease the player's elevation. Each iteration has been stuck on a 2D plane.

What I'm looking for is a Monument Valley kind of deal, I even have a script from a MV-style template (see below) but I can't figure out how to hand-place slopes.

Any advice on where I should look or what I can do with the code below would be great!

 using UnityEngine;
 using System.Collections;
 
 public class MoveCharacter : MonoBehaviour {
 
     enum SlopeType {
         slope,
         slope_up,
         slope_down,
         floor,
         floor_down,    // 아랫부분이 내려가는 경사
         floor_up,    // 아랫부분이 올라가는 경사
         half,
     };
 
     struct MoveData {
         public Vector3 dir;
         public GameObject destFloor;
         public MoveData(Vector3 dir, GameObject obj) {
             this.dir = dir;
             this.destFloor = obj;
         }
     };
 
     float moving = 0;
     float updown = 0;
     bool half_check = false;
     Vector3 moveDest = Vector3.zero;
     GameObject standingFloor = null;
     GameObject destFloor = null;
     MoveData[] moveList = null;
     int moveListIndex = 0;
 
 
     public GameObject StandingFloor {
         get {
             return standingFloor;
         }
     }
 
     Vector3 ArrangePos(Vector3 vec) {
         float x = vec.x;
         float y = vec.y;
         float z = vec.z;
 
         if (Mathf.Abs (transform.up.x) > 0.125) {
             vec.x = z;
             vec.y = -x;
             vec.z = -y;
         }
         else if (Mathf.Abs (transform.up.z) > 0.125) {
             vec.x = -y;
             vec.y = -z;
             vec.z = x;
         }
 
         return vec;
     }
 
     Vector3 ArrangeDir(Vector3 vec) {
         float x = vec.x;
         float y = vec.y;
         float z = vec.z;
         
         if (Mathf.Abs (transform.up.x) > 0.125) {
             vec.x = -y;
             vec.y = -z;
             vec.z = x;
         }
         else if (Mathf.Abs (transform.up.z) > 0.125) {
             vec.x = z;
             vec.y = -x;
             vec.z = -y;
         }
         
         return vec;
     }
 
     Vector3 FloorFunc(Vector3 pos) {
         Vector3 v = ArrangePos (pos);
         return new Vector3(v.x + v.y, v.z + v.y, v.y);
     }
 
     Vector3 FloorFuncWithoutArrange(Vector3 v) {
         return new Vector3(v.x + v.y, v.z + v.y, v.y);
     }
     
     Vector3 PlayerFloorValue() {
         Vector3 pos = transform.position;
         pos -= transform.up;
         if (updown != 0)
             pos += transform.up * 0.5f;
 
         return FloorFunc(pos);
     }
 
     Vector3 SlopeNormal(GameObject slope) {
         Mesh mesh = slope.GetComponent<MeshFilter>().mesh; 
         return slope.transform.rotation * mesh.normals[10];
     }
     /*
     Vector3 PlayerNormal() {
         return transform.up;
     }
     */
     SlopeType GetSlopeType(GameObject slope) {
         Vector3 slope_normal = SlopeNormal (slope);
         Vector3 player_normal = transform.up;
         float dot = Vector3.Dot (slope_normal, player_normal);
         if (dot > 0.125f) {
             return SlopeType.slope;
         } else if (dot < -0.125f) {
             return SlopeType.floor;
         } else {
             return SlopeType.half;
         }
     }
 
     SlopeType GetSlopeType(GameObject slope, Vector3 move_dir) {
         Vector3 slope_normal = SlopeNormal (slope);
         Vector3 player_normal = transform.up;
         float dot1 = Vector3.Dot (slope_normal, player_normal);
         float dot2 = Vector3.Dot (slope_normal, move_dir);
         if (dot1 > 0.125f) {
             if (dot2 > 0.125f)
                 return SlopeType.slope_down;
             else if (dot2 < -0.125f)
                 return SlopeType.slope_up;
             else return SlopeType.half;
         } else if (dot1 < -0.125f) {
             if (dot2 > 0.125f)
                 return SlopeType.floor_up;
             else if (dot2 < -0.125f)
                 return SlopeType.floor_down;
             else return SlopeType.half;
         } else {
             return SlopeType.half;
         }
     }
 
     GameObject FindFloorWithFloorValue(Vector3 floor_pos, bool find_next = false) {
         GameObject[] floors = GameObject.FindGameObjectsWithTag("Floor");
 
         GameObject target = null;    // 찾는 위치
         Vector3 target_pos = Vector3.zero;
 
         GameObject lid = null;        // 찾는 위치 바로 위에 있는 바닥(뚜껑)
         Vector3 lid_pos = new Vector3(floor_pos.x + 1, floor_pos.y + 1, floor_pos.z + 1);
 
         foreach (GameObject obj in floors) {
             Vector3 pos = FloorFunc (obj.transform.position);
             if (floor_pos.x == pos.x && floor_pos.y == pos.y
                 && (!find_next || pos.z < floor_pos.z)
                 && (target == null || pos.z > target_pos.z)) {
                 target = obj;
                 target_pos = pos;
             }
             else if (lid_pos.x == pos.x && lid_pos.y == pos.y
                      && (lid == null || pos.z > lid_pos.z)) {
                 lid = obj;
                 lid_pos = pos;
             }
         }
 
         if (target == null || (lid && lid_pos.z == target_pos.z + 1)) {
             return null;
         }
 
         return target;
     }
 
     void MoveTo(GameObject obj, Vector3 dir) {
         updown = 0;
         if (standingFloor.name[0] == 'S') {
             SlopeType type = GetSlopeType(standingFloor, dir);
             if (type == SlopeType.slope_up) {
                 updown = 1;
             }
             else if (type == SlopeType.slope_down) {
                 updown = -1;
             }
             else if (type == SlopeType.half) {
                 return;
             }
         }
 
         transform.rotation = Quaternion.LookRotation(dir, transform.up);
 
         moveDest = obj.transform.position;
         moveDest += transform.up;
         destFloor = obj;
         moving = 1;
         float maxZ = CalcMaxZ (FloorFunc (destFloor.transform.position));
         if (maxZ > PlayerFloorValue().z) ChangeZ (maxZ);
     }
 
     void Move(Vector3 dir) {
         Vector3 object_pos = standingFloor.transform.position;
         Vector3 object_floor = FloorFunc (object_pos);
         
         Vector3 arranged_dir = ArrangeDir(dir);
         Vector3 floor_value_offset = FloorFuncWithoutArrange(dir);
         Vector3 next_floor = object_floor + floor_value_offset;
         next_floor.z = 1024;
         
         if (standingFloor.name[0] == 'S') {
             print("Slope");
             SlopeType type = GetSlopeType(standingFloor, arranged_dir);
             if (type == SlopeType.half) return;
             else if (type == SlopeType.slope_down) {
                 next_floor.x -= 1;
                 next_floor.y -= 1;
                 object_floor.z -= 1;
             }
         }
         
         GameObject other;
         Vector3 other_floor = Vector3.zero;
         while (other = FindFloorWithFloorValue(next_floor, true)) {
             other_floor = FloorFunc(other.transform.position);
             
             if (other_floor.z > object_floor.z &&
                 next_floor.x + next_floor.y > object_floor.x + object_floor.y &&
                 !(other.name[0] == 'S' &&
               GetSlopeType(other, arranged_dir) == SlopeType.floor_down)) {
                 next_floor.z = other_floor.z;
                 continue;
             }
             else if (other_floor.z < object_floor.z &&
                      next_floor.x + next_floor.y < object_floor.x + object_floor.y &&
                      !(standingFloor.name[0] == 'S' &&
               GetSlopeType(standingFloor, arranged_dir) == SlopeType.floor_up)) {
                 next_floor.z = other_floor.z;
                 continue;
             }
             MoveTo(other, arranged_dir);
             return;
         }
         
         next_floor.x += 1;
         next_floor.y += 1;
         next_floor.z = 1024;
         while (other = FindFloorWithFloorValue(next_floor, true)) {
             other_floor = FloorFunc(other.transform.position);
             if (other.name[0] == 'S'
                 && GetSlopeType(other, arranged_dir) == SlopeType.slope_up) {
                 MoveTo(other, arranged_dir);
                 return;
             }
             next_floor.z = other_floor.z;
         }
             
     }
 
     public void StopMoving () {
         moving = 0;
         destFloor = null;
         moveList = null;
         moveListIndex = 0;
 //        RecalcPlayerPos ();
     }
 
     void ChangeZ(float z) {
         float deltaZ = z - PlayerFloorValue ().z;
         if (deltaZ == 0) return;
         Vector3 pos = transform.position;
         transform.position = new Vector3(pos.x - deltaZ, pos.y + deltaZ, pos.z - deltaZ);
     }
 
     float CalcMaxZ(Vector3 floor_value) {
         GameObject left_top_obj;
         GameObject right_top_obj;
 
         Vector3 left_top_pos = new Vector3 (floor_value.x, floor_value.y + 1, 1024);
         Vector3 right_top_pos = new Vector3 (floor_value.x + 1, floor_value.y, 1024);
 
         float maxZ = floor_value.z;
         float tmpZ;
 
         while (left_top_obj = FindFloorWithFloorValue (left_top_pos, true)) {
             tmpZ = FloorFunc(left_top_obj.transform.position).z;
             if (tmpZ > maxZ
                 && left_top_obj.name[0] == 'S'
                 && GetSlopeType(left_top_obj, ArrangeDir (Vector3.forward)) == SlopeType.floor_down) {
                 maxZ = tmpZ;
                 break;
             }
             left_top_pos.z = tmpZ;
         }
 
         while (right_top_obj = FindFloorWithFloorValue (right_top_pos, true)) {
             tmpZ = FloorFunc(right_top_obj.transform.position).z;
             if (tmpZ > maxZ
                 && right_top_obj.name[0] == 'S'
                 && GetSlopeType(right_top_obj, ArrangeDir (Vector3.right)) == SlopeType.floor_down) {
                 maxZ = tmpZ;
                 break;
             }
             right_top_pos.z = tmpZ;
         }
 
         return maxZ;
     }
 
     void RecalcPlayerZ() {
         Vector3 player_pos = PlayerFloorValue ();
         ChangeZ (CalcMaxZ (player_pos));
     }
 
     public void RecalcPlayerPos() {
         Vector3 player_pos = standingFloor.transform.position;
         Vector3 angle = transform.rotation.eulerAngles;
         transform.rotation = Quaternion.Euler (Mathf.Round(angle.x), Mathf.Round(angle.y), Mathf.Round(angle.z));
 
         updown = 0;
         if (standingFloor.name[0] == 'S') {
             SlopeType type = GetSlopeType(standingFloor, transform.forward);
             if (type == SlopeType.slope_up) {
                 updown = 1;
             }
             else if (type == SlopeType.slope_down) {
                 updown = -1;
             }
         }
 
         player_pos += transform.up;
         if (updown != 0)
             player_pos -= transform.up * 0.5f;
         
         transform.position = player_pos;
         RecalcPlayerZ();
         // player_pos = transform.position;
         // transform.position = new Vector3 (Mathf.Round (player_pos.x), Mathf.Round (player_pos.y), Mathf.Round (player_pos.z));
     }
 
     // Use this for initialization
     void Start () {
         Init();
     }
 
     public void Init() {
         moving = 0;
         updown = 0;
         half_check = false;
         moveDest = Vector3.zero;
         standingFloor = null;
         destFloor = null;
         moveList = null;
         moveListIndex = 0;
 
         transform.rotation = Quaternion.identity;
         standingFloor = FindFloorWithFloorValue (PlayerFloorValue());
         RecalcPlayerZ ();
     }
     
     // Update is called once per frame
     void Update () {
 
         if (Input.GetMouseButtonDown (0)) {
             Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
             RaycastHit hit;
             if (Physics.Raycast(ray, out hit))
             {
                 GameObject obj = hit.transform.gameObject;
                 
                 if(hit.collider.gameObject.tag == "Floor")
                     FindPathTo(obj);
 
                 if (hit.collider.gameObject.tag == "Activate")
                     hit.collider.gameObject.GetComponent<Activation>().Activate();
             }
         }
 
         if (moving > 0) {
             moving -= Time.deltaTime * 4;
             transform.Translate ((Vector3.forward + Vector3.up * updown) * Time.deltaTime * 4, Space.Self);
             if (moving <= 0) {
                 transform.position = moveDest;
                 standingFloor = destFloor;
                 destFloor = null;
                 half_check = false;
                 RecalcPlayerPos();
                 if (moveList != null) {
                     moveListIndex += 1;
                     if (moveListIndex < moveList.Length) {
                         MoveTo(moveList[moveListIndex].destFloor, moveList[moveListIndex].dir);
                     } else {
                         moveList = null;
                         moveListIndex = 0;
                     }
                 }
             }
             else if (!half_check && moving < 0.5) {
                 half_check = true;
                 updown = 0;
                 if (destFloor.name[0] == 'S') {
                     SlopeType type = GetSlopeType(destFloor, transform.forward);
                     if (type == SlopeType.slope_up) {
                         updown = 1;
                     }
                     else if (type == SlopeType.slope_down) {
                         updown = -1;
                     }
                 }
             }
             return;
         }
 
         if (moveList != null) return;
     }
 
 
     class AStarItem{
         GameObject obj;
         Vector3 floor_value;
         int weight;
         int GWeight, HWeight;
         public Vector3 dir;    // 이 타일로 오는 방향
         public AStarItem parent;
 
         public AStarItem(GameObject obj, Vector3 floor_value, int gweight, int hweight, Vector3 dir) {
             this.obj = obj;
             this.floor_value = floor_value;
             this.GWeight = gweight;
             this.HWeight = hweight;
             weight = gweight + hweight;
             this.dir = dir;
         }
 
         public GameObject Obj {
             get { return obj; }
         }
 
         public Vector3 FloorValue {
             get { return floor_value; }
         }
 
         public int Weight {
             get { return weight; }
         }
 
         public int G {
             get { return GWeight; }
             set { GWeight = value; weight = GWeight + HWeight; }
         }
 
         public int H {
             get { return HWeight; }
             set { HWeight = value; weight = GWeight + HWeight; }
         }
     };
 
     class ItemComparer : IComparer {
         public int Compare (object x, object y){
             return CompareItem ((AStarItem) x, (AStarItem) y);
         }
 
         public int CompareItem(AStarItem a, AStarItem b) {
             return a.Weight.CompareTo (b.Weight);
         }    
     }
 
     void FindPathTo(GameObject dest) {
         ArrayList open_list = new ArrayList ();
         ArrayList closed_list = new ArrayList ();
         IComparer compare = new ItemComparer ();
         open_list.Add (new AStarItem (standingFloor, FloorFunc (standingFloor.transform.position), 0, 0, Vector3.zero));
 
         Vector3 dest_floor = FloorFunc (dest.transform.position);
         AStarItem dest_item = null;
 
         while (open_list.Count > 0) {
             open_list.Sort(compare);
             AStarItem item = (AStarItem) open_list[0];
             open_list.RemoveAt(0);
             closed_list.Add (item);
 
             if (item.Obj == dest) {
                 dest_item = item;
                 break;
             }
 
             AStarItem[] near_items = FindNearFloors(item.Obj);
             for (int i = 0; i < near_items.Length; i++) {
                 if (near_items[i] == null) continue;
                 AStarItem near_item = near_items[i];
                 bool ok = true;
                 foreach (AStarItem it in closed_list) {
                     if (it.Obj == near_item.Obj) {
                         ok = false;
                         break;
                     }
                 }
                 if (!ok) continue;
 
                 near_item.G = item.G + 1;
                 near_item.H = (int) (Mathf.Abs(dest_floor.x - near_item.FloorValue.x) + Mathf.Abs(dest_floor.y - near_item.FloorValue.y));
                 near_item.parent = item;
 
                 for (int j = 0; j < open_list.Count; j++) {
                     AStarItem it = (AStarItem) open_list[j];
                     if (it.Obj == near_item.Obj) {
                         ok = false;
                         it.G = Mathf.Max (it.G, near_item.G);
                         break;
                     }
                 }
                 if (!ok) continue;
 
                 open_list.Add(near_item);
             }
         }
 
         if (dest_item != null) {
             ArrayList move_list = new ArrayList();
             AStarItem item = dest_item;
             while (item.Weight > 0) {
                 move_list.Add (new MoveData(item.dir, item.Obj));
                 item = item.parent;
             }
             move_list.Reverse();
 
             if (moving > 0){
                 StopMoving ();
                 RecalcPlayerPos ();
             }
 
             moveList = new MoveData[move_list.Count];
             for (int i = 0; i < move_list.Count; i++) {
                 moveList[i] = (MoveData)move_list[i];
             }
 
             MoveTo(moveList[0].destFloor, moveList[0].dir);
         }
     }
 
     AStarItem[] FindNearFloors(GameObject obj) {
         Vector3[] dirs = new Vector3[]{
             Vector3.forward,
             Vector3.right,
             Vector3.back,
             Vector3.left,
         };
 
         AStarItem[] items = new AStarItem[dirs.Length];
 
         Vector3 object_pos = obj.transform.position;
         Vector3 object_floor = FloorFunc (object_pos);
 
         for (int i = 0; i < dirs.Length; i++) {
             Vector3 dir = dirs[i];
             Vector3 arranged_dir = ArrangeDir(dir);
             Vector3 floor_value_offset = FloorFuncWithoutArrange(dir);
             Vector3 next_floor = object_floor + floor_value_offset;
             next_floor.z = 1024;
 
             if (obj.name[0] == 'S') {
                 SlopeType type = GetSlopeType(obj, arranged_dir);
                 if (type == SlopeType.half) continue;
                 else if (type == SlopeType.slope_down) {
                     next_floor.x -= 1;
                     next_floor.y -= 1;
                     object_floor.z -= 1;
                 }
             }
 
             GameObject other;
             Vector3 other_floor = Vector3.zero;
             while (other = FindFloorWithFloorValue(next_floor, true)) {
                 other_floor = FloorFunc(other.transform.position);
 
                 if (other_floor.z > object_floor.z &&
                     next_floor.x + next_floor.y > object_floor.x + object_floor.y &&
                     !(other.name[0] == 'S' &&
                       GetSlopeType(other, arranged_dir) == SlopeType.floor_down)) {
                     next_floor.z = other_floor.z;
                     continue;
                 }
                 else if (other_floor.z < object_floor.z &&
                          next_floor.x + next_floor.y < object_floor.x + object_floor.y &&
                          !(obj.name[0] == 'S' &&
                           GetSlopeType(obj, arranged_dir) == SlopeType.floor_up)) {
                     next_floor.z = other_floor.z;
                     continue;
                 }
                 break;
             }
 
             if (other) {
                 items[i] = new AStarItem(other, other_floor, 0, 0, arranged_dir);
                 continue;
             }
 
             next_floor.x += 1;
             next_floor.y += 1;
             next_floor.z = 1024;
             while (other = FindFloorWithFloorValue(next_floor, true)) {
                 other_floor = FloorFunc(other.transform.position);
                 if (other.name[0] == 'S'
                     && GetSlopeType(other, arranged_dir) == SlopeType.slope_up) {
                     break;
                 }
                 next_floor.z = other_floor.z;
             }
 
             if (other) {
                 items[i] = new AStarItem(other, other_floor, 0, 0, arranged_dir);
                 continue;
             }
 
         }
 
         return items;
     }
 
 }
Comment
Add comment
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

0 Replies

· Add your reply
  • Sort: 

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

39 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

Isometric tilemap with 3d characters? 2 Answers

Navmesh path to best of several destinations 0 Answers

Any PathFinding in 3D space plugin with collision support recommend? 0 Answers

3D Floating Enemy Pathfinding 0 Answers

For Dimetric Game, use Unity 2D or 3D? 0 Answers


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges