【Unity】项目源码——2D横版过关类游戏《A_Standard_Runner》
2015-06-15 01:40
691 查看
【摘要】这同样是sunset在学习Unity游戏制作过程中独立制作的游戏,游戏的操作与过关方式比较简单,整体制作时间大概两到三天中除去上课吃饭以及睡觉的剩余时间。虽然也出现了意想不到的小问题,不过整体制作过程中还算比较顺利。这篇博客就来说说如何制作一款这样的游戏的核心部分。
适合游戏故事背景的萌萌的主人公,也就是玩家控制的角色模型。
适合游戏故事背景的萌萌的小骷髅,也就是逻辑意义上的敌人。
适合游戏故事背景的场景模型以及障碍物模型,因为游戏发生在现代都市,所以可以是车辆,树木,建筑物等等。
合适的音频文件
一颗耐心,静静去完成属于自己的独立作品。
同时,sunset在这个脚本中使用代码模拟了人物移动惯性,也就是说,人物跑动过程中并不会因为玩家松开了移动的按钮而停止向前运动,而会再向前运动一段距离知道当前速度为0后,才可转向或不转向进行新的移动。当按下Jump按钮后,人物会向上跳跃,在跳跃离开地面的时刻起初始跳跃速度(向量:既有方向又有大小)就会因为受到重力的影响而不断减小甚至改变方向直到落地后归为0;同时如果在跳跃之前按下向上键+Jump键,则能跳的更高。jump是利用角色控制器组件的功能进行实现的,如果有所疑问就查API,有详细解释以及示例,真真实实的。人物的AnimatorController是这样的:
这里的walk状态和Run状态之间的切换是通过角色当前的移动速度进行判定的,其实这里使用混合树(blendTree)是更好的,但是sunset在当时制作时没有考虑到,之后事情很多也就没心情再去改了,提及一下。
代码如下:
这里sunset采用的是当玩家移动到敌人巡逻的两点之间的时候,即玩家人物的X坐标大小处于两点的X坐标大小之间,就让敌人进行Chase状态,对玩家进行追踪,如果碰到玩家,则玩家死亡,敌人开始Eat的动作,是不是萌萌的,哈哈。当移动到巡逻的两个边界点上时就进行嘲讽(Taunt)状态,并在一定时间后继续朝另一个边界点进行巡逻运动。下面是敌人的状态机:
XMargin和YMargin值可以在Inspector视图中进行自由的修改。而Camera的跟随主要是利用 Mathf.Lerp()方法进行实现的。XSmooth和YSmooth主要用于影响Camera移动过程中的平滑程度。
嗯,这款游戏的核心部分主要就是这些了,如果还有其他自己想要实现的部分,可以再细细琢磨,慢慢修改。
接下来,上试玩图片:
补充:源码及资源下载地址:http://pan.baidu.com/s/14R1me
1.游戏背景简介
这款游戏的故事背景大概讲的是一群不知为何从地底复活的古生物——萌萌的小骷髅为了实现蠢蠢的想法而想要占领城市,它们各种破坏城市,制作了各种妨碍人们正常生活的事,此时一位热衷于跑步的少年出现,他的出现显然不是为了拯救城市,而只是一心一意的专注于奔跑,然而在这个过程却又要免于小骷髅的骚扰,故事就从这里开始了。。。2.我们所需要的
制作任何一款游戏都离不开合适于游戏的人物模型,所以我们先需要一下这些东西:适合游戏故事背景的萌萌的主人公,也就是玩家控制的角色模型。
适合游戏故事背景的萌萌的小骷髅,也就是逻辑意义上的敌人。
适合游戏故事背景的场景模型以及障碍物模型,因为游戏发生在现代都市,所以可以是车辆,树木,建筑物等等。
合适的音频文件
一颗耐心,静静去完成属于自己的独立作品。
3.编写人物移动代码
在此类游戏中,最重要的就是玩家操作感受,于是优先考虑移动代码的编写是非常需要的,虽然这个游戏的过程是2D游戏,但是sunset在创建场景等一切模型元素上采用的都是3D模型,所以游戏整体上在视角方面更有层次感。因为我们这是2D游戏,并不会在平面上突然出现一个斜坡,所以人物的移动只需要使用this.transform.Translate()即可,但是人物身上还是需要载有CharacterController组件以用于进行角色跳跃。然而这都不是最难的,因为角色控制器的存在,无法使用刚体的一切属性,所以就不得不对游戏场景的重力进行模拟,只有一遍一遍不断的尝试才能找到合适感觉。接下来是代码:using UnityEngine; using System.Collections; [RequireComponent(typeof(Animator))] public class RunnerController : MonoBehaviour { //public Variable public enum Direction { Forward = 90, Backward = 270, } public float maxSpeed; public float MoveSpeed = 0; public float Z; public float JumpForce; public float Gravity; public bool PlayerDead; //private variable private CharacterController Controller; private Animator animator; private Direction direction = Direction.Forward; private float H; private float V; private bool OnGround = true; private bool Jump; private float JSpeed; void Awake() { //rigidbody = this.GetComponent<Rigidbody>(); Controller = this.GetComponent<CharacterController>(); animator = this.GetComponent<Animator>(); } void Update () { if(!PlayerDead) { if(this.transform.position.z != Z) { Vector3 Position = this.transform.position; Position.z = Z; this.transform.position = Position; } H = Input.GetAxis("Horizontal"); V = Input.GetAxis("Vertical"); //AniamtorState(); if(Controller.isGrounded) { OnGround = true; Jump = false; animator.SetBool("Jump",false); if(Input.GetKeyDown(KeyCode.J)) { if(V > 0.3) { JSpeed = 1.3f * JumpForce; } else { JSpeed = JumpForce; } Jump = true; animator.SetBool("Jump", true); } } else { OnGround = false; if(!OnGround) { Controller.Move(-Vector3.up * Gravity * Time.deltaTime); } } } else { animator.SetBool("Dead",true); animator.SetFloat("speed", 0); animator.SetFloat("Slider", 0); animator.SetBool("Jump", false); } } void FixedUpdate() { JumpUp(); Move(); } void Move() { //先计算朝向 if(H >= 0.3) { if(MoveSpeed == 0) { SetFacingDirection(Direction.Forward); } if(direction == Direction.Forward) { if(MoveSpeed < maxSpeed) { MoveSpeed += 1.0f; //state = State.Walk; } else { MoveSpeed = maxSpeed; //state = State.Run; } } } else if(H <= -0.3) { if(MoveSpeed == 0) { SetFacingDirection(Direction.Backward); } if(direction == Direction.Backward) { if(MoveSpeed < maxSpeed) { MoveSpeed += 1.0f; //state = State.Walk; } else { MoveSpeed = maxSpeed; //state = State.Run; } } } if((direction == Direction.Forward && H < -0.3 && MoveSpeed != 0) || (direction == Direction.Backward && H > 0.3 && MoveSpeed != 0)) { MoveSpeed -= 1.0f; if(MoveSpeed <= 0) { MoveSpeed = 0; //state = State.Idle; } } if(Mathf.Abs(H) < 0.3 && MoveSpeed != 0) { MoveSpeed -= 0.5f; if(MoveSpeed <= 0) { MoveSpeed = 0; //state = State.Run; } } transform.Translate(Vector3.forward * MoveSpeed * Time.deltaTime); animator.SetFloat("Speed", MoveSpeed); animator.SetFloat("Slider", V); } void JumpUp() { if(Jump) { JSpeed -= 2 * Gravity * Time.deltaTime; Controller.Move(Vector3.up * Time.deltaTime * JSpeed); } } void SetFacingDirection(Direction dir) { if(direction != dir) { transform.Rotate(Vector3.up * (direction - dir)); direction = dir; } } void OnTriggerEnter(Collider _collider) { if(_collider.gameObject.tag == "Enemy") { PlayerDead = true; } } }
同时,sunset在这个脚本中使用代码模拟了人物移动惯性,也就是说,人物跑动过程中并不会因为玩家松开了移动的按钮而停止向前运动,而会再向前运动一段距离知道当前速度为0后,才可转向或不转向进行新的移动。当按下Jump按钮后,人物会向上跳跃,在跳跃离开地面的时刻起初始跳跃速度(向量:既有方向又有大小)就会因为受到重力的影响而不断减小甚至改变方向直到落地后归为0;同时如果在跳跃之前按下向上键+Jump键,则能跳的更高。jump是利用角色控制器组件的功能进行实现的,如果有所疑问就查API,有详细解释以及示例,真真实实的。人物的AnimatorController是这样的:
这里的walk状态和Run状态之间的切换是通过角色当前的移动速度进行判定的,其实这里使用混合树(blendTree)是更好的,但是sunset在当时制作时没有考虑到,之后事情很多也就没心情再去改了,提及一下。
4)怪物AI代码
2D游戏的怪物AI相较于3D游戏会简单许多,我们只需要让敌人在两点之间循环进行巡逻,并在目标点进行短暂的休息或者说嘲讽动作。所以我们需要在敌人可移动到的两个点上创建连个空物体,分别指示给需要在两点之间巡逻的敌人即可。移动方面仍然使用transform.Translate()方法。代码如下:
using UnityEngine; using System.Collections; public class SKT_AI : MonoBehaviour { public enum State { Idle, Walk, Run, Death, Eat, } public Transform[] _MovePoints = new Transform[2]; public float _WaitTimer; public float _WalkSpeed; public float _RunSpeed; [HideInInspector] public bool _Dead; //private Variable private State _state; private State _Laststate = State.Idle; private GameObject _Player; private Vector3 _TargetPosition; private int _PointIndex; private Animator _animator; private float _Speed; private bool _playerDead = false; private float _WaitTime; void Awake() { _Player = GameObject.FindGameObjectWithTag("Player"); _animator = this.GetComponent<Animator>(); } void Start() { _TargetPosition = _MovePoints[0].position; } void FixedUpdate() { if(_state != State.Death) { if(_Dead) { _state = State.Death; } else { if((_Player.transform.position.x > _MovePoints[0].position.x) && (_Player.transform.position.x < _MovePoints[1].position.x)) { Chase(); } else { MoveAround(); } } } } void MoveAround() { float _Distance = Vector3.Distance(_TargetPosition, this.transform.position); if(_Distance > 1.0f) { _state = State.Walk; this.transform.LookAt(_TargetPosition); this.transform.Translate(Vector3.forward * _Speed * Time.deltaTime); } else { if(_Laststate == State.Run) { _TargetPosition = new Vector3(_MovePoints[0].position.x, this.transform.position.y, this.transform.position.z); _Laststate = State.Idle; } _state = State.Idle; _WaitTime -=Time.deltaTime; if(_WaitTime <= 0.0f) { if(_TargetPosition.x == _MovePoints[0].position.x) { _TargetPosition = _MovePoints[1].position; } else if(_TargetPosition.x == _MovePoints[1].position.x) { _TargetPosition = _MovePoints[0].position; } _WaitTime = _WaitTimer; } } JudgeState(); } void Chase() { if(!_playerDead) { _state = State.Run; _TargetPosition = new Vector3(_Player.transform.position.x, this.transform.position.y, this.transform.position.z); this.transform.LookAt(_TargetPosition); this.transform.Translate(Vector3.forward * _Speed * Time.deltaTime); } else { _state = State.Eat; } _Laststate = State.Run; JudgeState(); } void JudgeState() { if(_state == State.Idle) { _animator.SetBool("Run", false); _animator.SetBool("Walk", false); _animator.SetBool("Eat", false); _Speed = 0.0f; } else if(_state == State.Walk) { _animator.SetBool("Run", false); _animator.SetBool("Eat", false); _animator.SetBool("Walk", true); _Speed = _WalkSpeed; } else if(_state == State.Run) { _animator.SetBool("Run", true); _animator.SetBool("Eat", false); _animator.SetBool("Walk", false); _Speed = _RunSpeed; } else if(_state == State.Eat) { _animator.SetBool("Eat", true); _animator.SetBool("Run", false); _animator.SetBool("Walk", false); _Speed = 0.0f; } else if(_state == State.Death) { _animator.SetBool("Dead", true); _animator.SetBool("Run", false); _animator.SetBool("Walk", false); _animator.SetBool("Eat", false); _Speed = 0.0f; Destroy(this.gameObject, 3); } } }
这里sunset采用的是当玩家移动到敌人巡逻的两点之间的时候,即玩家人物的X坐标大小处于两点的X坐标大小之间,就让敌人进行Chase状态,对玩家进行追踪,如果碰到玩家,则玩家死亡,敌人开始Eat的动作,是不是萌萌的,哈哈。当移动到巡逻的两个边界点上时就进行嘲讽(Taunt)状态,并在一定时间后继续朝另一个边界点进行巡逻运动。下面是敌人的状态机:
5)Camera的设置
在2D游戏中当角色移动的时候,Camera需要跟随角色进行移动,但也不是不论角色移动怎样的距离都进行移动,所以需要一个X轴上的Margin值来限定Camera不移动的人物移动最大距离。代码:public float XMargin; public float YMargin; public float XSmooth = 8.0f; public float YSmooth = 8.0f; public Vector3 MaxXAndY; public Vector3 MinXAndY; public Transform Target; bool CheckXMargin() { return Mathf.Abs(transform.position.x - Target.position.x) > XMargin; } bool CheckYMargin() { return Mathf.Abs(transform.position.y - Target.position.y) < YMargin + 89.0f; } void FixedUpdate() { FollowTarget(); } void FollowTarget() { float targetX = transform.position.x; float targetY = transform.position.y; if(CheckXMargin()) { targetX = Mathf.Lerp(transform.position.x, Target.position.x, XSmooth * Time.deltaTime); } if(CheckYMargin()) { targetY = Mathf.Lerp(transform.position.y, Target.position.y + targetY, YSmooth * Time.deltaTime); } transform.position = new Vector3(targetX, transform.position.y, transform.position.z); } }
XMargin和YMargin值可以在Inspector视图中进行自由的修改。而Camera的跟随主要是利用 Mathf.Lerp()方法进行实现的。XSmooth和YSmooth主要用于影响Camera移动过程中的平滑程度。
嗯,这款游戏的核心部分主要就是这些了,如果还有其他自己想要实现的部分,可以再细细琢磨,慢慢修改。
接下来,上试玩图片:
补充:源码及资源下载地址:http://pan.baidu.com/s/14R1me
相关文章推荐
- unity开发关于IOS平台接入之自动化构建管线
- Unity 5.1 重大发布,新功能全力支持VR开发
- <Unity UGUI><EasyTouch> 使用EasyTouch, 摇杆在Dynamic模式下,点击UI控件也会弹出的问题解决
- Unity Notes之配置文件基于内容的差异化更新
- 【Unity】项目源码——简单2D空战游戏
- Unity中 Plugin 跨语言 类型转换
- Unity项目对 git版本控制库扩展插件
- unity, do nothing的state
- unity, stateMachine, change state name
- Shader基础实例之动画序列帧播放
- 【Unity】Finite State Machine 有限状态机
- Unity 5事件系统
- 【Unity小工具】批量修改原始资源设置
- unity 中用vs 打开cs脚本找不到关联类
- [Unity3D]自己动手重制坦克舰队ArmadaTank(2)从碰撞说起
- unity3d 安卓发布
- 【原创】Unity T4M 中文讲解
- unity3d easytouch教程
- Unity3d中制作Loading场景进度条(转)
- unity 第三人称