Unity开发一个FPS游戏之二

这篇具有很好参考价值的文章主要介绍了Unity开发一个FPS游戏之二。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

在之前的文章中,我介绍了如何开发一个FPS游戏,添加一个第一人称的主角,并设置武器。现在我将继续完善这个游戏,打算添加敌人,实现其智能寻找玩家并进行对抗。完成的效果如下:

fps_enemy_demo

 

下载资源

首先是设计敌人,我们可以在网上找到一些好的免费素材,例如在Unity商店里面有一个不错的免费素材, Low Poly Soldiers Demo | 3D 角色 | Unity Asset Store,里面提供了3D模型和动画,其收费版提供了更多的模型,武器以及动画,收费10美刀也不贵。这里我以收费版为素材,把其加到我之前开发的FPS游戏中。

下载资源后导入到项目中,然后在项目文件的LowPoly Soldiers的prefab目录下,可以看到有多个不同服装和武器的Prefab。选择一个拖动到项目中的Prefab目录。然后打开Prefab,在模型中找到其武器,在其枪口位置新增一个名为muzzle的空的GameObject,这将作为敌人发射子弹的位置。如下图:

Unity开发一个FPS游戏之二,游戏开发,unity,游戏,游戏引擎,c#

点击这个Prefab的根对象,即左侧导航树的Soldier_marine variant,为其增加一个Capsule collider,调整其设置,使得这个Collider能覆盖整个人物。

增加一个Script的组件,重用之前创建的MuzzleEffect脚本。

添加一个Nav Mesh Agent组件,使得敌人能在烘培的路面上具备自动寻路的功能。

烘培路线

要让敌人能自动寻路,需要在场景中进行路线的烘培。打开菜单的Window->AI->Navigation,选择Bake,设置Agent的相关参数,然后点击Bake按钮即可。

定义敌人状态和动画切换

在项目的Scripts目录新增一个名为WanderingAI的Script文件,定义敌人的行为模式。这里我们可以定义敌人有行走,快跑,瞄准,射击,重载弹药,受伤害和死亡这几种行为。为了方便起见,在这个Script里面可以定义一个enum来统一管理当前的状态,如以下代码:

[Flags]
private enum EnemyStatus {
    Idle, 
    Walk,
    Aim,
    AimLeft,
    AimRight,
    Shoot,
    Reload,
    Sprint,
    Damage,
    Death
}

在以上状态中,瞄准状态有三个,分别对应原地瞄准,向左移动瞄准和向右移动瞄准。

另外再定义其他的一些属性,并进行初始化,如以下代码:

[Header("Enemy speed")]
public float speed = 3.0f;
public float sprintSpeed = 5.0f;

[Header("Enemy eyeview")]
public float eyeviewDistance = 500.0f;
public float viewAngle = 120f;
public float obstacleRange = 1.0f;

[Header("Enemy behavior")]
public float aimPeriod = 3.0f;

[Header("Shoot Setting")]
[SerializeField]
private Transform muzzleTransform;
[SerializeField] GameObject bulletPrefab;
public int ammo = 5;

[Header("Enemy life")]
public int health = 10;

private Animator _animator;
private Collider[] SpottedPlayers;
private GameObject bullet;
private MuzzleEffect muzzleEffect;
private long shootTS = 0;
private Vector3 prevPlayerPosition = new Vector3(100f, 100f, 100f);
private Vector3 enemyMuzzleDelta = new Vector3(0f, 2.0f, 0f);
private Vector3 playerPositionDelta = new Vector3(0f, 1.0f, 0f);
private Vector3 playerDirection;
private int currentAmmo; 
private float lerpValue = 10f;
private bool randomSearch = true;
private NavMeshAgent _agent;
private Coroutine corDiscoverPlayer = null;
private Coroutine corReload = null;
private Vector3 attackDirection;
private EnemyStatus status;
private EnemyStatus prevStatus;

void Start()
{
    _animator = GetComponent<Animator>();
    muzzleEffect = GetComponent<MuzzleEffect>();
    _agent = GetComponent<NavMeshAgent>();
    currentAmmo = ammo;
    status = EnemyStatus.Idle;
}

在项目的Animator目录,新建一个Animator controller文件,把LowPoly Soldiers的animation目录下需要用到的动画拖动到窗口,定义动画状态的切换。

Unity开发一个FPS游戏之二,游戏开发,unity,游戏,游戏引擎,c#

 在上图的左侧定义了相关的参数,主要是Trigger类型的,用于切换不同的动画状态。其中还有两个参数是Bool类型,用于判断动画是否播放完毕。在动画切换的Transition中,如果是由运动的动画切换到相对静止的动画,例如从Aim_Left切换到Shoot,需要开启Has Exit Time选项,这样就能平滑的过渡。如果其他不需要平滑过渡,需要立即切换状态的,则Transition不要选择Has Exit Time。

对于Damage和Reload这几个动画,需要添加一个Script,里面的OnStateExit函数需要设置animator.SetBool("E_IsDamage", false); 或者animator.SetBool("E_IsReload", false); 这样我们可以通过获取相应的参数来判断是否播放完毕。

检测玩家

当敌人没有发现玩家时,敌人处于随机搜索状态,这时敌人步行巡逻,当将要碰到障碍物时随机转向,如以下代码:

void Walk() {
    float distance = DetectObstacle(transform.forward);
    if (distance < obstacleRange) {
        float angle = UnityEngine.Random.Range(-110, 110);
        transform.Rotate(0, angle, 0);
    }
    transform.Translate(0, 0, speed * Time.deltaTime);
}

float DetectObstacle(Vector3 direction) {
    Ray ray = new Ray(transform.position, direction);
    RaycastHit hit;
    if (Physics.SphereCast(ray, 0.75f, out hit)) {
        return hit.distance;
    } else {
        return 9999.0f;
    }
}

在敌人巡逻的过程中,将检测其前方目视范围内是否有发现玩家,我们可以使用Physics.OverlapSphere函数,将检测以敌人当前位置为圆心,以视野范围为半径的球体内满足一定条件的所有碰撞体的集合,然后判断检测到的物体是否处于敌人的视野范围内,并且中间没有障碍物阻挡,如果满足条件,意味着敌人发现了玩家。然后我们可以进一步扩展这个检测之后触发的状态,当敌人发现玩家,我们可以进一步判断,是要先进入瞄准状态还是直接进入射击状态。考虑到这个检测是每次Update都调用的,如果在上一帧调用时第一次检测到了玩家,那么应该先进入瞄准模式,当下一帧调用时同样检测到了玩家,这时就不需要再次进入瞄准模式了,只需等待一段时间后进入射击模式即可。因此我们可以在每次检测到玩家时保存其位置,和之前保存的位置做比较,如果这个位置之间相差的范围超过一个阈值,则需要重新瞄准,否则的话直接射击。

如果没有检测到玩家,那么也分两种情况,一种情况是之前曾经检测过玩家,并保存了玩家的位置,这意味着玩家躲避敌人的攻击,从视野范围中消失。这时敌人应该迅速跑到玩家之前的位置,继续搜索。另一种情况就是之前没有检测到玩家,这时应该继续步行随机搜索的状态。

以下是检测玩家的代码:

void DetectPlayer() {
    Vector3 position = transform.position;
    SpottedPlayers = Physics.OverlapSphere(transform.position, eyeviewDistance, LayerMask.GetMask("Character"));
    
    for (int i=0;i<SpottedPlayers.Length;i++) {
        Vector3 playerPosition = SpottedPlayers[i].transform.position;

        if (Vector3.Angle(transform.forward, playerPosition - position) <= viewAngle/2) {
            RaycastHit info = new RaycastHit();
            int layermask = LayerMask.GetMask("Character", "Default");
            Physics.Raycast(position, playerPosition - position, out info, eyeviewDistance, layermask);
            if (info.collider == SpottedPlayers[i]) {
                randomSearch = false;
                playerDirection = playerPosition - prevPlayerPosition;
                float distance = (playerDirection).magnitude;
                if (distance > 0.5f || prevStatus != EnemyStatus.Shoot) {
                    prevPlayerPosition = playerPosition;
                    switch(UnityEngine.Random.Range(1, 4)) {
                        case 1:
                            _animator.SetTrigger("E_Aim");
                            status = EnemyStatus.Aim;
                            break;
                        case 2:
                            if (DetectObstacle(-transform.right) >= 3.0f) {
                                _animator.SetTrigger("E_Aim_L");
                                status = EnemyStatus.AimLeft;
                            } else {
                                _animator.SetTrigger("E_Aim");
                                status = EnemyStatus.Aim;
                            }
                            break;
                        case 3:
                            if (DetectObstacle(transform.right) >= 3.0f) {
                                _animator.SetTrigger("E_Aim_R");
                                status = EnemyStatus.AimRight;
                            } else {
                                _animator.SetTrigger("E_Aim");
                                status = EnemyStatus.Aim;
                            }
                            break;
                    }
                }
                _agent.isStopped = true;
                if (status == EnemyStatus.Aim || status == EnemyStatus.AimLeft) {
                    corDiscoverPlayer = StartCoroutine(DiscoverPlayer());
                } else {
                    StartCoroutine(DiscoverPlayer());
                }
                return;
            }
        }
    }

    // Can not detect player
    if (randomSearch) {
        if (status != EnemyStatus.Walk) {
            _animator.SetTrigger("E_Walk");
            status = EnemyStatus.Walk;
        }
    } else {
        if (status != EnemyStatus.Sprint) {
            _animator.SetTrigger("E_Sprint");
            status = EnemyStatus.Sprint;
            _agent.destination = prevPlayerPosition - playerPositionDelta;
            _agent.isStopped = false;
            _agent.speed = sprintSpeed;
        }
    }

    if (corDiscoverPlayer != null) {
        StopCoroutine(corDiscoverPlayer);
    }
    return;
}

在以上代码中,当检测到玩家时,敌人将进入瞄准或射击状态并启动一个协程运行DiscoverPlayer,采用协程的原因是,需要瞄准一段时间之后才能射击。在Shoot函数中,会判断当前的弹药,如果为0,则启动一个协程运行Reload。如果弹药不为0,则按照一定的射速来发射子弹。另外,考虑到游戏难度,特意在发射子弹时,给其位置增加一点随机的小的偏移量,这样即使敌人瞄准了玩家,也不一定能打准。其代码如下:

private IEnumerator DiscoverPlayer() {
    if (status == EnemyStatus.Aim || status == EnemyStatus.AimLeft || status == EnemyStatus.AimRight) {
        yield return new WaitForSecondsRealtime(aimPeriod);
    }
    Shoot();
}

private void Shoot() {
    if (currentAmmo==0) {
        corReload = StartCoroutine(Reload());
        return;
    }
    long nowTS = DateTime.UtcNow.Ticks;
    float shootInterval = (nowTS - shootTS)/10000000.0f;
    status = EnemyStatus.Shoot;
    if (shootInterval >= 0.5) {
        TurnToPlayer();
        _animator.SetTrigger("E_Shoot");
        Vector3 bulletPosition = new Vector3(UnityEngine.Random.Range(-0.1f, 0.1f), UnityEngine.Random.Range(-0.1f, 0.1f), 0f) + muzzleTransform.position;
        bullet = Instantiate(bulletPrefab, bulletPosition, transform.rotation);
        Vector3 tempForward = prevPlayerPosition - muzzleTransform.position;
        bullet.GetComponent<Rigidbody>().velocity = new Vector3(tempForward.x, 0f, tempForward.z) * 5.0f;
        muzzleEffect.Effect(muzzleTransform.position);
        shootTS = nowTS;
        currentAmmo--;
    }
}

private IEnumerator Reload() {
    _animator.SetTrigger("E_Reload");
    status = EnemyStatus.Reload;
    _animator.SetBool("E_IsReload", true);
    yield return new WaitUntil(()=>!_animator.GetBool("E_IsReload"));
    currentAmmo = ammo;
    status = EnemyStatus.Idle;
}

 如果没有检测到玩家,则判断当前是否要随机搜索,如果是,则设置状态为Walk并进行搜索,如果不是,意味着之前是有检测到玩家的,则根据之前保存的玩家的位置,快跑到该位置,这里是采用Nav Mesh Agent的自动导航功能来实现。

射击敌人

在上一篇博客中,我们定义了玩家可以发射子弹,并且进行碰撞判断,那么当碰撞的物体是敌人时,应该扣除敌人的生命值。

修改之前的Bullet.cs程序的OnCollisionEnter,另外要把敌人预制件的Tag设置为Enemy

private void OnCollisionEnter(Collision collision) {
    if (collision.gameObject.CompareTag("Enemy")) {
        WanderingAI behavior = collision.gameObject.GetComponent<WanderingAI>();
        behavior.TakeDamage(damage, transform.forward);
    }
    Destroy(this.gameObject);
}

在WanderingAI.cs中,增加一个TakeDamage函数和相应的其他函数,当受到伤害时,需要立即切换到受伤害的动画,之前如果有未完成的Reload或者DiscoverPlayer的协程,需要立即关闭。如果敌人的health为0,则切换到Death的动画,并在等待一段时间后销毁该Gameobject。如以下代码:

public void TakeDamage(int damage, Vector3 direction) {
    if (health > 0) {
        health -= damage;
        attackDirection = -direction;
        if (corDiscoverPlayer != null) {
            StopCoroutine(corDiscoverPlayer);
            corDiscoverPlayer = null;
        }
        if (corReload != null) {
            StopCoroutine(corReload);
            corReload = null;
        }
        
        if (health <= 0) {
            StartCoroutine(Death());
        } else {
            Damage();
        }
    }
}

private void Damage() {
    switch(UnityEngine.Random.Range(1, 4)) {
        case 1:
            _animator.SetTrigger("E_Damage_A");
            break;
        case 2:
            _animator.SetTrigger("E_Damage_B");
            break;
        default:
            _animator.SetTrigger("E_Damage_C");
            break;
    }
    _animator.SetBool("E_IsDamage", true);
    status = EnemyStatus.Damage;
}

private IEnumerator Death() {
    switch(UnityEngine.Random.Range(1, 4)) {
        case 1:
            _animator.SetTrigger("E_Death_A");
            break;
        case 2:
            _animator.SetTrigger("E_Death_B");
            break;
        default:
            _animator.SetTrigger("E_Death_C");
            break;
    }
    status = EnemyStatus.Death;
    yield return new WaitForSecondsRealtime(6.0f);
    Destroy(this.gameObject);
}

状态处理

定义了以上关键的检测玩家和瞄准射击的功能,以及敌人的不同状态后,我们可以在Update函数里面根据不同的状态来调用不同的功能。如以下代码:

void Update()
{        
    if (status == EnemyStatus.AimLeft || status == EnemyStatus.AimRight) {
        AimMove();
    } else if (status == EnemyStatus.Reload || status == EnemyStatus.Death) {
        return;
    } else if (status == EnemyStatus.Aim) {
        TurnToPlayer();
    } else if (status == EnemyStatus.Damage) {
        TurnToDamage();
        if (!_animator.GetBool("E_IsDamage")) {
            status = EnemyStatus.Idle;
            prevPlayerPosition = new Vector3(100f, 100f, 100f);
        }
    } else {
        if (status == EnemyStatus.Walk) {
            Walk();
        }
        if (status == EnemyStatus.Sprint) {
            Sprint();
        }
        DetectPlayer();
    }
    prevStatus = status;
}

void TurnToPlayer() {
    Vector3 targetDirection = (prevPlayerPosition - transform.position).normalized;
    transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(targetDirection), lerpValue*Time.deltaTime);
}

void TurnToDamage() {
    transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(attackDirection), lerpValue*Time.deltaTime);
}

void AimMove() {
    TurnToPlayer();
    float distance = 0f;
    if (status == EnemyStatus.AimLeft) {
        distance = DetectObstacle(-transform.right);
    }
    if (status == EnemyStatus.AimRight) {
        distance = DetectObstacle(transform.right);
    }
    if (distance < 0.5) {
        _animator.SetTrigger("E_Aim");
        status = EnemyStatus.Aim;
    } else {
        if (status == EnemyStatus.AimLeft) {
            transform.Translate(speed * Time.deltaTime * -Vector3.right);
        }
        if (status == EnemyStatus.AimRight) {
            transform.Translate(speed * Time.deltaTime * Vector3.right);
        }
    }
}

void Sprint() {
    if (_agent.remainingDistance < 3.0f) {
        _agent.isStopped = true;
        randomSearch = true;
        _animator.SetTrigger("E_Walk");
        status = EnemyStatus.Walk;
        transform.forward = playerDirection;
    }
}

解释一下以上的代码,当瞄准状态为AimLeft或者AimRight的时候,因为需要向旁边移动,所以需要先判断一下距离旁边障碍物的距离是否大于一个阈值,如果不是则把状态设置为静止的Aim。当状态为Sprint的时候,敌人将快速跑到玩家之前出现的位置,当距离这个位置很接近时,将停止并切换为随机搜索状态。

设置玩家生命值

当敌人发射子弹的时候,如果子弹碰撞到玩家,那么玩家的生命值需要扣减。我们需要在游戏界面上用显示当前的生命值,可以用一个血槽来显示。

在上一篇博客中,我们创建了一个名为GameScreen的Canvas预制体,在其上显示当前的弹药数。同样我们需要在这个预制体里面增加生命值的显示控件。在这个预制体上创建一个新的UI->Slider,命名为Health。设置其Scale X和Y都为3。

在Assests的Images目录下,导入一个新的Asset,选择一张16*16大小的白色图片,类型选择为UI Sprite。

点击Health slider下的background,颜色RGB都设置为0,Alpha设置为102。

点击Fill Area下的Fill,拖动之前导入的白色图片到Source Image,Color设置为红色。然后把Fill从Fill Area的子物体拖动到上一层,和Fill Area平级。

现在改动Health slider的Value,可以看到红色血量可以跟随Value值变动,但是可以看到Fill的区域和Background的区域没有对齐。 分别选择Fill和Background,点击Rect Transform的Stretch按钮,然后按着Alt键选择右下方的Stretch。这样两个区域就能对齐。

最后可以在添加一个十字Icon的Image在这个血槽的左边,以达到更美观的效果。

增加一个新的消息类型来传递当前的生命值给新加的Health UI组件。编辑GameMessage.cs文件,增加一个新的消息,如下:

public interface IGameMessage : IEventSystemHandler
{
    ...
    void HealthMessage(float currentHealth);
}

 编辑UIController.cs文件,增加对Health组件的引用和消息的处理

public class UIController : MonoBehaviour, IGameMessage
{
    ...
    [SerializeField] Slider playerHealth;


    public void HealthMessage(float health) {
        playerHealth.value = health;
    }
}

编辑PlayerController.cs文件,增加一个TakeDamage方法来扣减Health

    [Header("Player")]
    ...
    [Tooltip("Player health")]
    public int PlayerHealth = 10;

    ...
    private int _health;

    private void Start()
    {
        ...
        _health = PlayerHealth;
        ExecuteEvents.Execute<IGameMessage>(_gameManager, null, (x,y)=>x.HealthMessage(1.0f));
    }

    public void TakeDamage(int damage) {
        _health -= damage;
        float healthValue = (float) _health/PlayerHealth;
        ExecuteEvents.Execute<IGameMessage>(_gameManager, null, (x,y)=>x.HealthMessage(healthValue));
    }

最后就是修改bullet.cs,当判断碰撞的物体Tag是Player时,调用PlayerController的TakeDamage方法。

private void OnCollisionEnter(Collision collision) {
    ...
    if (collision.gameObject.CompareTag("Player")) {
        PlayerController player = collision.gameObject.GetComponent<PlayerController>();
        player.TakeDamage(damage);
    }
    Destroy(this.gameObject);
}

 现在生命值的处理就完成了。

玩家受到攻击的闪红处理

通常当玩家受到伤害时,屏幕都会用红色闪现一下,代表玩家掉血。现在继续完善添加这个效果。

在GameScreen下增加一个新的Canvas,名字为FeedbackFlashCanvas,然后在其下新增一个名为FlashImage的Canvas Group,Source Image的color为红色,Alpha为255。

在GameScreen新增一个Script组件,名字为FeedbackFlash.cs,代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class FeedbackFlash : MonoBehaviour
{
    [Header("References")] [Tooltip("Image component of the flash")]
    public Image FlashImage;

    [Tooltip("CanvasGroup to fade the damage flash, used when recieving damage end healing")]
    public CanvasGroup FlashCanvasGroup;

    [Header("Damage")] [Tooltip("Color of the damage flash")]
    public Color DamageFlashColor;

    [Tooltip("Duration of the damage flash")]
    public float DamageFlashDuration;

    [Tooltip("Max alpha of the damage flash")]
    public float DamageFlashMaxAlpha = 1f;

    bool m_FlashActive;
    float m_LastTimeFlashStarted = Mathf.NegativeInfinity;

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        if (m_FlashActive) {
            float normalizedTimeSinceDamage = (Time.time - m_LastTimeFlashStarted) / DamageFlashDuration;

            if (normalizedTimeSinceDamage < 1f)
            {
                float flashAmount = DamageFlashMaxAlpha * (1f - normalizedTimeSinceDamage);
                FlashCanvasGroup.alpha = flashAmount;
            }
            else
            {
                FlashCanvasGroup.gameObject.SetActive(false);
                m_FlashActive = false;
            }
        }
    }

    void ResetFlash()
    {
        m_LastTimeFlashStarted = Time.time;
        m_FlashActive = true;
        FlashCanvasGroup.alpha = 0f;
        FlashCanvasGroup.gameObject.SetActive(true);
    }

    public void OnTakeDamage()
    {
        ResetFlash();
        FlashImage.color = DamageFlashColor;
    }
}

把刚才建的FlashImage拖动到Script的相应属性。Damage Flash Color设置为红色,Alpha为255,Flash Duration设置为0.2, Flash max alpha设置为0.7 

回到UIController.cs这个脚本,在收到HealthMessage的时候调用FeedbackFlash的OnTakeDamage,这样就可以出现红色闪烁的效果。

public class UIController : MonoBehaviour, IGameMessage
{
    ...
    private FeedbackFlash _flash;

    void Start()
    {
        _flash = GetComponent<FeedbackFlash>();
    }

    public void HealthMessage(float health) {
        ...
        _flash.OnTakeDamage();
    }
}

总结

以上就是对这个FPS游戏增加了具备智能行为的敌人的相关设计介绍,可以看到整个游戏的可玩性有了进一步的提高。下一步将进行关卡的设计,完善游戏场景。文章来源地址https://www.toymoban.com/news/detail-840863.html

到了这里,关于Unity开发一个FPS游戏之二的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • unity制作FPS射击游戏

    角色动作方面包括行走、奔跑、跳跃、武器切换、弹夹更换、武器展示、弹壳抛出效果、射击效果、全自动与半自动射击效果、瞄准效果、后坐力效果、弹痕效果等多种动作。 非玩家角色(NPC)具备多个动画状态,包括固定路径巡逻、行走、奔跑、寻路攻击等多种行为。 太

    2024年02月08日
    浏览(45)
  • 使用团结引擎开发Unity 3D射击游戏

           本案例是初级案例,意在引导想使用unity的初级开发者能较快的入门,体验unity开发的方便性和简易性能。       本次我们将使用团结引擎进行开发,帮助想体验团结引擎的入门开发者进行较快的环境熟悉。      本游戏是一个俯视角度的射击游戏。主角始终位于屏幕

    2024年01月19日
    浏览(59)
  • Unity、UE、Cocos游戏开发引擎的区别

    Unity、Unreal Engine(UE)和Cocos引擎是三个常用的游戏开发引擎,它们在功能和特性上有一些区别。以下是它们之间的主要区别: 编程语言:Unity使用C#作为主要的编程语言,开发者可以使用C#脚本进行游戏逻辑编写。Unreal Engine主要使用C++作为编程语言,但也支持蓝图系统,允许

    2024年02月22日
    浏览(50)
  • 【Unity实战】实现一款简单的FPS游戏

    实现一款FPS游戏需要以下步骤: 1.创建场景:在Unity中创建3D场景,设定地形、灯光、天气等环境,新增角色、武器等道具。 2.角色控制:创建角色,并添加Unity内置的角色控制器或自定义控制器脚本,处理角色的移动、射击、跳跃、动作等。 3.武器系统:创建武器模型,添加

    2024年02月06日
    浏览(36)
  • Unity游戏源码分享-射击游戏Low Poly FPS Pack 3.2

    Unity游戏源码分享-射击游戏Low Poly FPS Pack 3.2 项目地址:https://download.csdn.net/download/Highning0007/88057717    

    2024年02月16日
    浏览(50)
  • Unity 开发人员转CGE(castle Game engine)城堡游戏引擎指导手册

    一、简介 2. Unity相当于什么GameObject? 3. 如何设计一个由多种资产、生物等组成的关卡? 4. 在哪里放置特定角色的代码(例如生物、物品)?Unity 中“向 GameObject 添加 MonoBehaviour”相当于什么? 5.Unity子目录相当于什么Assets? 6. 支持哪些模型格式? 7. 支持FBX模型格式吗? 8.

    2024年02月07日
    浏览(70)
  • 【制作100个unity实战之2】实现一款简单的FPS游戏

    实现一款FPS游戏需要以下步骤: 1.创建场景:在Unity中创建3D场景,设定地形、灯光、天气等环境,新增角色、武器等道具。 2.角色控制:创建角色,并添加Unity内置的角色控制器或自定义控制器脚本,处理角色的移动、射击、跳跃、动作等。 3.武器系统:创建武器模型,添加

    2024年02月04日
    浏览(53)
  • 【Unity】Unity开发学习和项目实践02——创建第一个Unity项目和游戏物体

    创建第1个Unity项目 打开Unity hub,点击新项目 以下有四处地方需要注意选择: 1.Unity编辑器版本 2.项目模板 3.项目名称 4.项目保存位置 点击创建项目 ok,进入编辑器了 把编辑器界面布局稍微改一下,改成2by3 点击Edit 点击 project settings,这是对我们所创建工程的设置 此外还有对

    2024年01月25日
    浏览(41)
  • 【unity小技巧】两种办法解决FPS游戏枪或者人物穿墙穿模问题

    当我们开发FPS游戏时(其实3d游戏基本都会遇到这样的问题),如果我们不做处理,肯定会出现人物或者枪的穿墙穿模问题,这是是一个常见的挑战。 这种问题会破坏游戏的真实性和可玩性,使玩家的体验受到影响。在这篇文章中,我将分享一些Unity小技巧,介绍如何解决F

    2024年02月03日
    浏览(55)
  • 吐槽laya:H5小游戏开发应该用什么引擎好?laya、cocos还是unity?

    我看有人推荐laya,放在H5小游戏的前三排名,这压根不靠谱。 laya只能算个半成品,整体非常垃圾,如果是首次选择游戏引擎,至少转去cocos,实在选laya,那也没办法了。 下面说说laya有什么问题,如果只是一些简单的bug什么的,我是不会花这个时间吐槽的,但是如下的问题实

    2024年02月13日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包