游戏开发实战"/>
三维游戏开发实战
1、需求分析
- 地形设计,对游戏地形进行设计比如挖坑造河等。
- 场景设计,种树、种草、摆放房屋建筑等。
- 第一人称视角实现,WASD前后左右移动,空格键进行跳跃,按左Shift进行加速。
- 敌人的简单制作,敌人动画设计,寻路攻击玩家等。
- 场景的灯光设计,Bake。
- 天空盒制作设计。
- UI设计,玩家生命力、得分、游戏胜利失败。
- 小地图设计。
- 特效制作与设计。
- 添加一个主场景,开始游戏和退出游戏。
- 添加一个加载场景,进度条的设计与制作。
- 添加第一关场景,搭建制作第一关场景。
- 添加第二关场景,搭建制作第二关场景。
- 添加第三关场景,搭建制作第三关场景。
- 最后测试发布。
- 解决bug并优化。
功能结构图如下:
-
详细设计
(1)场景搭建
第一关场景的搭建,第一关的场景搭建了一个卡通的森林,这个场景主要就是包含了一些石头、树木花草什么的。然后在添加一个星空天空盒,基本上第一关场景就是这样了。
第二关场景的搭建,第二关场景搭建了一个比第一关更加卡通好看的场景,这个场景包含了流水、小桥、房屋、树木花草、云朵、天空盒、山、绚丽的粒子特效和灯光。总结来说创造了一个“行云流水”,“小桥流水人家”,“袅袅炊烟”,的一个世外桃源之地。
第三关场景的搭建,第三关场景主要就是搭建了一个石头窟,基本上就是由石头块构成。
未完待续场景,这个主要是用来退出的场景。
开始场景搭建,这个主要用来开始游戏。
加载场景搭建,这个主要就是用来作为过渡场景。
(2)玩家控制设计
玩家控制主要实现了WASD前后左右移动,按空格键进行跳跃,按住shift进行加速,然后控制摄像机进行视角的控制,然后可以开火射击敌人。
- 敌人控制设计
敌人主要就是实现了走、攻击、死亡动画,通过unity寻路组件进行寻路进而找到玩家进行攻击。
- UI设计
UI的话,主要了三部分,第一部分就是设计了一个开始游戏的界面UI,这个比较简单,还有一个就是加载场景进度条界面,稍微东西多一点的就是游戏主要界面,这个界面主要包含玩家生命力、得分、小地图、游戏胜利、游戏失败、下一关、退出游戏等元素。
- 添加音效
一个游戏怎么能少音效呢,在这里主要包含了一个恐怖的背景音乐和一个射击音效。
- 添加特效
一个游戏怎么能没有特效呢,这里主要添加了玩家射击特效以及水火烟等特效,有了这些特效以后游戏的整体感觉就不一样了。
- 碰撞检测与射线检测
这里通过unity自带的碰撞盒进行碰撞检测,这个实现起来还是比较简单的,射线检测主要就是通过从摄像机发出一条射线,然后通过判断射线与那个物体进行了碰撞。
到这里大部分功能就都已经实现了,最后就需要优化测试发布了。
3、编程实现
3.1场景搭建
(格式说明:说明文字、附截图与关键操作步骤/核心代码)
- 开始场景搭建:
- 加载场景搭建:
- 第一关场景搭建:
- 第二关场景搭建:
- 第三关场景搭建:
- 未完待续场景搭建:
3.2角色控制
- 玩家控制
核心代码:
public class Player : MonoBehaviour{public Transform m_transform;CharacterController m_ch;float m_movSpeed = 3.0f;float m_gravity = 2.0f;public int m_life = 5;//摄像机TransformTransform m_camTransform;//摄像机旋转角度Vector3 m_camRot;//摄像机高度float m_camHeight = 1.4f;//枪口transformTransform m_muzzlepoint;//射击时可以碰撞层public LayerMask m_layer;public Transform m_fx;public AudioClip m_audio;float m_shootTimer = 0;private void Start(){m_transform = this.transform;m_ch = this.GetComponent<CharacterController>();m_camTransform = Camera.main.transform;m_camTransform.position = m_transform.TransformPoint(0, m_camHeight, 0);m_camTransform.rotation = m_transform.rotation;m_camRot = m_camTransform.eulerAngles;//Screen.lockCursor = true;Cursor.visible = false;m_muzzlepoint = GameObject.FindGameObjectWithTag("muzzlepoint").transform;}private void Update(){if(m_life<=0){return;}else{Control();}m_shootTimer -= Time.deltaTime;//射击if(Input.GetMouseButton(0) && m_shootTimer<=0){m_shootTimer = 0.1f;this.GetComponent<AudioSource>().PlayOneShot(m_audio);GameManager.Instance.SetAmmo(1);RaycastHit info;bool hit = Physics.Raycast(m_muzzlepoint.position, m_camTransform.TransformDirection(Vector3.forward), out info, 100, m_layer);if(hit){if(info.transform.tag.CompareTo("enemy")==0){Enemy enemy = info.transform.GetComponent<Enemy>();enemy.OnDamage(1);}Instantiate(m_fx, info.point, info.transform.rotation);}}}void Control(){float rh = Input.GetAxis("Mouse X");float rv = Input.GetAxis("Mouse Y");m_camRot.x -= rv;m_camRot.y += rh;m_camTransform.eulerAngles = m_camRot;Vector3 camrot = m_camTransform.eulerAngles;camrot.x = 0;camrot.z = 0;m_transform.eulerAngles = camrot;float xm = 0, ym = 0, zm = 0;ym -= m_gravity * Time.deltaTime;//控制前后左右移动if(Input.GetKey(KeyCode.W)){zm += m_movSpeed * Time.deltaTime;}else if (Input.GetKey(KeyCode.S)){zm -= m_movSpeed * Time.deltaTime;}if (Input.GetKey(KeyCode.A)){xm -= m_movSpeed * Time.deltaTime;}else if (Input.GetKey(KeyCode.D)){xm += m_movSpeed * Time.deltaTime;}m_ch.Move(m_transform.TransformDirection(new Vector3(xm, ym, zm)));m_camTransform.position = m_transform.TransformPoint(0, m_camHeight, 0);}public void OnDamage(int damage){m_life -= damage;GameManager.Instance.SetLife(m_life);if(m_life<=0){Cursor.visible = true;}}}
- 敌人控制
核心代码:
public class Enemy : MonoBehaviour{Transform m_transform;Animator m_ani;Player m_player;NavMeshAgent m_agent;float m_moveSpeed = 2.0f;float m_rotSpeed = 5.0f;float m_timer = 2;int m_life = 5;protected EnemySpawn m_spawn;void Start(){m_transform = this.transform;m_ani = this.GetComponent<Animator>();m_player = GameObject.FindGameObjectWithTag("Player").GetComponent<Player>();m_agent = GetComponent<NavMeshAgent>();m_agent.speed = m_moveSpeed;//设置寻路目标m_agent.SetDestination(m_player.m_transform.position);}void Update(){if(m_player.m_life<=0 || !m_spawn.enabled){return;}m_timer -= Time.deltaTime;AnimatorStateInfo stateInfo = m_ani.GetCurrentAnimatorStateInfo(0);//如果处于待机且不是过渡状态if(stateInfo.fullPathHash == Animator.StringToHash("Base Layer.idle") && !m_ani.IsInTransition(0)){m_ani.SetBool("idle", false);if(m_timer>0){return;}if(Vector3.Distance(m_transform.position,m_player.m_transform.position)<1.5f){m_agent.ResetPath();m_ani.SetBool("attack", true);}else{m_timer = 1;m_agent.SetDestination(m_player.m_transform.position);m_ani.SetBool("run", true);}}//如果处于跑步且不是过渡状态if(stateInfo.fullPathHash == Animator.StringToHash("Base Layer.run") && !m_ani.IsInTransition(0)){m_ani.SetBool("run", false);if(m_timer<0){m_agent.SetDestination(m_player.transform.position);m_timer = 1;}if (Vector3.Distance(m_transform.position, m_player.transform.position) <= 1.5f){m_agent.ResetPath();m_ani.SetBool("attack", true);}}//如果处于攻击且不是过渡状态if (stateInfo.fullPathHash == Animator.StringToHash("Base Layer.attack") && !m_ani.IsInTransition(0)){RotateTo();m_ani.SetBool("attack", false);if (stateInfo.normalizedTime>=1.0f){m_ani.SetBool("idle", true);m_timer = 2;}}if (stateInfo.fullPathHash == Animator.StringToHash("Base Layer.death") && !m_ani.IsInTransition(0)){m_ani.SetBool("death", false);if (stateInfo.normalizedTime>=1.0f){GameManager.Instance.SetScore(100);m_spawn.m_enemyCount--;Destroy(this.gameObject);}}if (stateInfo.fullPathHash == Animator.StringToHash("Base Layer.attack") && !m_ani.IsInTransition(0)){RotateTo();m_ani.SetBool("attack", false);if (stateInfo.normalizedTime >= 1.0f){m_ani.SetBool("idle", true);m_timer = 2;m_player.OnDamage(1);}}}protected void RotateTo(){Vector3 targetdir = m_player.m_transform.position - m_transform.position;Vector3 newDir = Vector3.RotateTowards(transform.forward, targetdir, m_rotSpeed * Time.deltaTime, 0.0f);m_transform.rotation = Quaternion.LookRotation(newDir);}public void OnDamage(int damage){m_life -= damage;if(m_life<=0){m_ani.SetBool("death", true);m_agent.ResetPath();}}public void Init(EnemySpawn spawn){m_spawn = spawn;m_spawn.m_enemyCount++;}}
动画设计:
3.3游戏ui设计
- 开始界面UI
核心代码:
public class Main : MonoBehaviour{Text title;Button startButton;Button quitButton;private void Start(){GameObject uicanvas = GameObject.Find("Canvas");foreach (Transform t in uicanvas.transform.GetComponentsInChildren<Transform>()){if (t.name.CompareTo("title") == 0){title = t.GetComponent<Text>();}else if (t.name.CompareTo("StartButton") == 0){startButton = t.GetComponent<Button>();startButton.onClick.AddListener(delegate ()//按钮事件{SceneManager.LoadScene(1);});}else if (t.name.CompareTo("QuitButton") == 0){quitButton = t.GetComponent<Button>();quitButton.onClick.AddListener(delegate ()//按钮事件{Application.Quit();});}}}}
进度条:
public class ProgressBar : MonoBehaviour{int level;Text sliderText;Image bg;public Slider _progress;//过渡场景图片名称private static string LoadSceneBg= "LoadSceneBg";private static int loadSceneBgNum = 1;void Awake(){_progress = GetComponent<Slider>();level = GameManager.level;GameObject uicanvas = GameObject.Find("Canvas");foreach (Transform t in uicanvas.transform.GetComponentsInChildren<Transform>()){if (t.name.CompareTo("sliderText") == 0){sliderText = t.GetComponent<Text>();}else if(t.name.CompareTo("Bg") == 0){bg= t.GetComponent<Image>();string path = LoadSceneBg + loadSceneBgNum;bg.overrideSprite = Resources.Load(path, typeof(Sprite)) as Sprite;loadSceneBgNum++;}}}//使用协程void Start(){StartCoroutine(LoadScene());}private void Update(){//显示进度条加载数值sliderText.text = _progress.value*100 + "/100";}IEnumerator LoadScene(){//用Slider 展示的数值int disableProgress = 0;int toProgress = 0;//异步场景切换AsyncOperation op = SceneManager.LoadSceneAsync(level);//关卡数加一GameManager.level++;//不允许有场景切换功能op.allowSceneActivation = false;//op.progress 只能获取到90%,最后10%获取不到,需要自己处理while (op.progress < 0.9f){//获取真实的加载进度toProgress = (int)(op.progress * 100);while (disableProgress < toProgress){++disableProgress;_progress.value = disableProgress / 100.0f;//0.01开始yield return new WaitForEndOfFrame();}}//因为op.progress 只能获取到90%,所以后面的值不是实际的场景加载值了toProgress = 100;while (disableProgress < toProgress){++disableProgress;_progress.value = disableProgress / 100.0f;yield return new WaitForEndOfFrame();}op.allowSceneActivation = true;}}
- 游戏主界面
核心代码:
[AddComponentMenu("Game/GameManager")]public class GameManager : MonoBehaviour{public static int level = 2;private static string transitionToLevel= "TransitionToLevel";public static GameManager Instance = null;public int m_score = 0;public static int m_hiscore = 0;public int m_ammo = 100;Player m_player;EnemySpawn m_enemySpawn;Text txt_ammo;Text txt_hiscore;Text txt_life;Text txt_score;Text txt_gameOver;Text txt_gameVictory;Button button_restart;Button button_nextGame;Button button_quitGame;Image hud;private void OnEnable(){Time.timeScale = 1;}private void Awake(){Instance = this;m_player = GameObject.FindGameObjectWithTag("Player").GetComponent<Player>();m_enemySpawn = GameObject.FindGameObjectWithTag("EnemySpawn").GetComponent<EnemySpawn>();GameObject uicanvas = GameObject.Find("Canvas");foreach (Transform t in uicanvas.transform.GetComponentsInChildren<Transform>()){if (t.name.CompareTo("txt_ammo") == 0){txt_ammo = t.GetComponent<Text>();}else if (t.name.CompareTo("txt_hiscore") == 0){txt_hiscore = t.GetComponent<Text>();txt_hiscore.text = m_hiscore.ToString();}else if (t.name.CompareTo("txt_life") == 0){txt_life = t.GetComponent<Text>();}else if (t.name.CompareTo("txt_score") == 0){txt_score = t.GetComponent<Text>();}else if (t.name.CompareTo("txt_gameOver") == 0){txt_gameOver = t.GetComponent<Text>();txt_gameOver.gameObject.SetActive(false);}else if (t.name.CompareTo("txt_gameVictory") == 0){txt_gameVictory = t.GetComponent<Text>();txt_gameVictory.gameObject.SetActive(false);}else if (t.name.CompareTo("hud") == 0){hud = t.GetComponent<Image>();}else if (t.name.CompareTo("Restart Button") == 0){button_restart = t.GetComponent<Button>();button_restart.onClick.AddListener(delegate ()//按钮事件{//Time.timeScale = 1;//读取关卡SceneManager.LoadScene(SceneManager.GetActiveScene().name);});//开始游戏按钮不可见button_restart.gameObject.SetActive(false);}else if (t.name.CompareTo("Next Button") == 0){button_nextGame = t.GetComponent<Button>();button_nextGame.onClick.AddListener(delegate ()//按钮事件{//Time.timeScale = 1;SceneManager.LoadScene(transitionToLevel);//level++;});//下一关游戏按钮不可见button_nextGame.gameObject.SetActive(false);}else if (t.name.CompareTo("Quit Button") == 0){button_quitGame = t.GetComponent<Button>();button_quitGame.onClick.AddListener(delegate ()//按钮事件{//Time.timeScale = 1;Application.Quit();});//退出游戏按钮不可见button_quitGame.gameObject.SetActive(false);}}}//更新分数public void SetScore(int score){m_score += score;if(m_score>0){m_hiscore += m_score;}txt_score.text = m_score.ToString();txt_hiscore.text = m_hiscore.ToString();}//更新弹药public void SetAmmo(int ammo){m_ammo -= ammo;if(m_ammo<=0){m_ammo = 100 - m_ammo;}txt_ammo.text = m_ammo.ToString() + "/100";}//更新生命public void SetLife(int life){txt_life.text = life.ToString();}//游戏失败public void GameOver(){if(m_player.m_life<=0){hud.gameObject.SetActive(false);txt_gameOver.gameObject.SetActive(true);button_restart.gameObject.SetActive(true);button_quitGame.gameObject.SetActive(true);m_enemySpawn.enabled = false;m_player.enabled = false;Time.timeScale = 0;Cursor.visible = true;}}//游戏胜利public void GameVictory(){if (m_player.m_life > 0 && m_score>=100){hud.gameObject.SetActive(false);txt_gameVictory.gameObject.SetActive(true);button_nextGame.gameObject.SetActive(true);button_quitGame.gameObject.SetActive(true);m_enemySpawn.enabled = false;m_player.enabled = false;Time.timeScale = 0;Cursor.visible = true;} }private void Update(){GameOver();GameVictory();}}
- 结束UI
3.4添加声音
添加背景音效和射击声
3.5添加粒子特效
- 射击粒子特效
- 火焰特效
- 烟特效
- 游戏测试
发布进行测试:
- 开始游戏
- 加载场景
3、第一关胜利与失败
4、第二关
5、第三关
- 结束
5、总结
这次实验项目做的是一个第一人称射击游戏,整体来说功能并不是非常多,不过在比较短的时间内可以做出来这样的一个效果我也算是比较满意了,只是还有一些功能想要实现却没能实现,比如按鼠标右键进行开镜射击,把枪换为刀进行近距攻击,手榴弹投掷等。其实我也是研究了一段时间的手榴弹投掷,只是做出了了一个抛物线轨迹,其实感觉也差不多快做出来了,只需要检测手榴弹落地点,然后让手榴弹按着实现的抛物线轨迹进行运动就可以了,这个只能有空搞了。总的来说,这个项目对我来说还是帮助很大的,让我学到了很多新知识也回顾了旧知识,对于自己独立解决问题,思考问题的能力有了进一步提升,相信在未来可以学的更好。
资源包下载地址:
更多推荐
三维游戏开发实战
发布评论