游戏人工智能(AI)开发(三)Part1

从本节开始我们通过做一个小的游戏,来给大家讲解AI中的巡逻机器人的功能。巡逻机器人是指在游戏中,如果对手没有出现(或者是没有发现敌人)的情况下,巡逻机器人在巡逻区域内自动来回行走,巡逻周围情况。一旦发现敌人,它将自动跟踪敌人并攻击敌人。和这种具有自动追击功能的机器人相对的是防守机器人,它们只守在自己的地方,一旦敌人靠得足够近(指在攻击范围内),就发起攻击。
在这节我们需要准备如下素材,一个能够做攻击动作和死亡动作的模型,一个图标作为Gizmo,用于在编辑器中帮助我们查看巡逻区域的网格。

上节的场景大家都熟悉了,上节的功能主要是,一群角色在一个区域,有一个玩家角色可以跑去追他们,在离的足够近的时候,这些角色将会逃跑。这些角色是不是显得太笨了?只会跑,什么都干不了,像唐僧一样。那么现在我们就做出一些机器人来保护这个“唐僧”。首先是做一个像孙悟空一样的角色,它能够在唐僧的周围巡逻,一旦发现有妖怪靠近(这里指我们自己,一不小心把自己的角色变成妖怪了。哈哈……)。孙悟空将追上“妖怪”并发动攻击。下面通过图来演示该游戏的功能。

游戏人工智能AI)开发

看清上面的逻辑之后,我们就可以开始写代码完成所需的功能了。这里“唐僧”的角色我们上节已经学会做了,他什么都不会,只会在妖怪来临的时候跑。
妖怪的角色,就是我们玩家控制的角色,现在我们要给他加一个生命的信息。在他活着的时候(生命指大于0的时候),他会跑向唐僧,要吃掉他(要吃唐僧的功能大家可以自己做)。在“孙悟空”攻击“妖怪”的时候,妖怪的血量下降。直至为0。妖怪死亡。

1. 首先我们要做一个“孙悟空”巡逻的区域。就是图中绿色的区域,它是由若干个点生成的一个区域。首先创建几个空物体全部命名为WayPoint,作为巡逻区域的限制点。新建一个C#代码段,命名为AutoWayPoint。输入如下代码

using UnityEngine;
using System.Collections;

public class AutoWayPoint : MonoBehaviour
{
public static ArrayList waypoints = new ArrayList(); //场景中所有的WayPoint
public ArrayList connected = new ArrayList(); //连接的节点
public static float kLineOfSightCapsuleRadius = 0.25f;

static AutoWayPoint closestWayPoint; //距离角色最近的节点

public static AutoWayPoint FindClosest(Vector3 pos) //查找到距离角色最近的WayPoint
{
// The closer two vectors, the larger the dot product will be.

foreach (AutoWayPoint cur in waypoints)
{
float distance = Vector3.Distance(cur.transform.position, pos);

if (distance < 100)
{
float closestDistance = distance;
closestWayPoint = cur;
}
}
return closestWayPoint;
}

//@ContextMenu ("Update Waypoints")
void UpdateWaypoints()
{
RebuildWaypointList();
}

void Awake()
{
RebuildWaypointList();
}

// Draw the waypoint pickable gizmo
void OnDrawGizmos()
{
Gizmos.DrawIcon(transform.position, "Waypoint.tif");
}

// Draw the waypoint lines only when you select one of the waypoints
void OnDrawGizmosSelected() //在编辑器中以连线的方式显示连接的点
{
if (waypoints.Count == 0)
RebuildWaypointList();
foreach (AutoWayPoint p in connected)
{
if (Physics.Linecast(transform.position, p.transform.position))
{
Gizmos.color = Color.red;
Gizmos.DrawLine(transform.position, p.transform.position);
}
else
{
Gizmos.color = Color.green;
Gizmos.DrawLine(transform.position, p.transform.position);
}
}
}

void RebuildWaypointList()
{
Object[] objects = FindObjectsOfType(typeof(AutoWayPoint));
waypoints = new ArrayList(objects);

foreach (AutoWayPoint point in waypoints)
{
point.RecalculateConnectedWaypoints();
}
}

void RecalculateConnectedWaypoints()
{
connected = new ArrayList();

foreach (AutoWayPoint other in waypoints)
{
// Don't connect to ourselves
if (other == this)
continue;

// Do we have a clear line of sight?
if (!Physics.CheckCapsule(transform.position, other.transform.position, kLineOfSightCapsuleRadius))
{
connected.Add(other);
}
}
}

}

保存,把代码拖给场景中所有的WayPoint物体,然后选择它们。我们应该能够看到场景中根据各个节点生成了一个网格,这个网格的边界就是“孙悟空“巡逻的区域。

2.为”孙悟空“添加巡逻并追击妖怪的本领。新建一个C#代码,变量如下

public Transform player;
public Transform hand;
public int speed = 3;
public GameObject fireBall;
public float rotationSpeed = 5.0f;
public float shootRange = 50.0f;
public float shootAngle = 4.0f;
public float dontComeCloserRange = 5.0f;
public float delayShootTime = 0.35f;
public float pickNextWaypointDistance = 10.0f;
public Transform target;
AnimationEvent attackEvent = new AnimationEvent();
float tempAttackTime;
private float nextFire = 0;

这里的变量主要包括,要攻击的角色,发射攻击的手的位置。攻击的武器,这里指一个小球。攻击频率,攻击距离等。
3. 巡逻功能,新建一个方法Patrol。该方法主要功能在于在”孙悟空“,没有发现”妖怪“前,沿着巡逻区域的边界巡逻。

IEnumerator Patrol()
{
AutoWayPoint curWayPoint = AutoWayPoint.FindClosest(transform.position);
while (true)
{
Vector3 waypointPosition = curWayPoint.transform.position;
if (Vector3.Distance(waypointPosition, transform.position) < pickNextWaypointDistance)
{
curWayPoint = PickNextWayPoint(curWayPoint);
}

if (CanSeeTarget())
{
yield return StartCoroutine(AttackPlayer());
}

MoveTowards(waypointPosition);
yield return null;
}
}

4. 判断是否能够看见”妖怪“,这里使用Physics.Linecast判断是否能看见妖怪。判断方法有很多,也可以根据距离判断,还可以使用OnTriggerEnter,或者RayCastHit等方法。我们这里用这个方法是想让妖怪躲在遮挡物后面的时候,不让”孙悟空“看见。
bool CanSeeTarget()
    {
        if (Vector3.Distance(transform.position, target.position) > 15)
        {
            return false;
        }

        RaycastHit hit;
        if (Physics.Linecast(transform.position, target.position, out hit))
        {
            return hit.transform == target;
        }
        else if (!Physics.Linecast(transform.position, target.position))
        {
            return true;
        }

        return false;

    }

AutoWayPoint PickNextWayPoint(AutoWayPoint currentWaypoint)
    {
        var forward = transform.TransformDirection(Vector3.forward);
        var best = currentWaypoint;
        var bestDot = -10.0;

        foreach (AutoWayPoint cur in currentWaypoint.connected)
        {
            var direction = Vector3.Normalize(cur.transform.position - transform.position);
            var dot = Vector3.Dot(direction, forward);
            if (dot > bestDot && cur != currentWaypoint)
            {
                bestDot = dot;
                best = cur;
            }
        }
        return best;
    }
}

5.然后是攻击的方法,当看见”妖怪“之后,发动攻击,直到”妖怪“死亡。在没发现之前,巡逻并搜索妖怪。

IEnumerator AttackPlayer()
{
var lastVisiblePlayerPosition = target.position;
while (true)
{
if (target == null)
{
yield return null;
}

var distance = Vector3.Distance(transform.position, target.position);
if (distance > shootRange * 3)
{
yield return null;
}

lastVisiblePlayerPosition = target.position;

if (distance > dontComeCloserRange)
{
MoveTowards(lastVisiblePlayerPosition);
}

if (distance 0.0)
{
MoveTowards(position);
if (CanSeeTarget())
{
yield return null;
}

timeOut -= Time.deltaTime;
yield return null;
}

}

7. 发射武器的方法。在攻击的时候,按照指定频率发射小球(这里指我们的攻击武器)。

void InstantiateBullet()
{
if (Time.time > nextFire)
{
nextFire = Time.time + 1.4f;
attackEvent.time = tempAttackTime;
animation["attack"].weight = 0.5f;
GameObject clone = Instantiate(fireBall, hand.position, hand.rotation) as GameObject;
Physics.IgnoreCollision(clone.collider, collider);
clone.rigidbody.velocity = transform.TransformDirection(Vector3.forward * 20);
BulletScript bulletScript = clone.gameObject.AddComponent();
bulletScript.playerShooting = this.gameObject;
}

}

8.在找到妖怪的时候,暂停巡逻,并转向“妖怪”

void RotateTowards(Vector3 position)
{
var direction = position - transform.position;
direction.y = 0;

if (direction.magnitude < 0.1)
{
return;
}

transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(direction), rotationSpeed * Time.deltaTime);

transform.eulerAngles = new Vector3(0, transform.eulerAngles.y, 0);

}

void MoveTowards(Vector3 position)
{
var direction = position - transform.position;
direction.y = 0;

if (direction.magnitude < .5)
{
return;
}

transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(direction), rotationSpeed * Time.deltaTime);
transform.eulerAngles = new Vector3(0, transform.eulerAngles.y, 0);

var forward = transform.TransformDirection(Vector3.forward);
var speedModifier = Vector3.Dot(forward, direction.normalized);
speedModifier = Mathf.Clamp01(speedModifier);

direction = forward * speed * speedModifier;

GetComponent().SimpleMove(direction);
animation.Play("run");
}

最后我们在Awake和Start函数中设置角色跑步,攻击和静止的动画层,

void Awake()
{
animation.AddClip(animation["run"].clip, "run2");
tempAttackTime = animation["attack"].clip.length;

}

void Start()
{
StartCoroutine( Patrol());
animation["idle"].layer = 1;
animation["run"].layer = 2;
animation["attack"].layer = 1;
animation["run2"].layer = 2;

attackEvent.functionName = "InstantiateBullet";
}

现在我们完成了”孙悟空“巡逻功能的开发。接下来的一节我们要为这个代码添加相关的妖怪血量的代码控制,武器攻击妖怪的控制能代码功能。

原文链接:http://www.unitymanual.com/thread-2588-1-1.html

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值