Unity3D学习笔记(7)—— 击球游戏
来源:互联网 发布:c语言奇数阶幻方 编辑:程序博客网 时间:2024/05/18 12:37
在下Simba,有何贵干!新一期的游戏又出炉了(明明就是个Demo啊喂),这次的游戏比以往都简单(你别骗我),但是比以往的都好看(Excuse me?),没错,那就是动画!这一期的游戏使用了以往都没有使用过的动画系统,而且用了别人模型(不要脸)。
Garen模型:http://pan.baidu.com/s/1i5exKNV
使用前请将所有模型和动作设置成Legacy动画, inspector -> Rig -> Animation type -> Legacy -> Apply button。
先来看看这酷炫的效果吧:(这...这莫非是...盖伦?)
游戏的规则很简单,玩家控制盖伦击打完场上的7个球游戏结束,并给出最终用时。
游戏的类图:
(说好的很简单呢?我怎么看不懂)不急不急,看上去很复杂,其实只有3个脚本要写,话说这图可能还有错,刚学的UML作图。
步骤说明:
一、场景布置
场景布置总是游戏开始制作时必须考虑和完善的事情,在打代码前得脑补出游戏正常运行时的情景。我设计的游戏摄像机是不动的,玩家可以在固定区域内通过WASD移动,点击鼠标左键进行攻击。当区域内所有小球都被击打消失后,游戏结束,显示玩家用时。
首先把玩家Garen拖入场景中,reset位置。新建4个正方体和1个平面,通过拉伸和位移组成一个方块区域:
给玩家添加刚体组件和碰撞盒,碰撞盒采用胶囊体:
新建1个UI Text对象,并将其定位在屏幕中央,用于显示玩家的最终用时:
场景中的所有对象:
二、玩家动作
OK,可以开始写代码了。想从最直观的写起,那么就先写玩家的控制吧。玩家有3个动作,分别是Idle、Run、Attack。Idle在Start时就播放,Run需要在Update中检测键盘输入,可以使用GetAxisRaw获得WASD的方向。Attack是比较复杂的动作,通过在动作过程中注册回调函数,可以使得在攻击动画播放过程中触发这些函数,比如AttackHit函数,以及StopAttack函数。前者判断有没有击中物体,并广播击中消息。后者使玩家播放站立动画(攻击动画结束后站立)。
using UnityEngine;using System.Collections;public class GarenMovement : MonoBehaviour { Animation ani; AnimationState idle; AnimationState run; AnimationState attack; public float speed = 5f; Vector3 movement; Rigidbody playerRigidbody; bool isAttacking = false; float rayLength = 1.8f; public delegate void AttackHitHandler(GameObject obj); public static event AttackHitHandler OnAttackHit; void Start() { playerRigidbody = this.GetComponent<Rigidbody>(); ani = this.GetComponent<Animation>(); idle = ani["Idle"]; run = ani["Run"]; attack = ani["Attack1"]; // 默认播放站立动画 idle.wrapMode = WrapMode.Loop; ani.Play(idle.clip.name); }void FixedUpdate () { if (!isAttacking) { float h = Input.GetAxisRaw("Horizontal"); float v = Input.GetAxisRaw("Vertical"); Move(h, v); } if (Input.GetMouseButtonDown(0)) { Attack(); }} void Move(float h, float v) { if (h != 0 || v != 0) { movement.Set(h, 0f, v); // 移动玩家位置 movement = movement.normalized * speed * Time.deltaTime; playerRigidbody.MovePosition(transform.position + movement); // 旋转玩家角度 Quaternion newRotation = Quaternion.LookRotation(movement); playerRigidbody.MoveRotation(newRotation); // 播放跑步动画 run.wrapMode = WrapMode.Loop; ani.CrossFade(run.clip.name, 0.1f); } else { ani.CrossFade(idle.clip.name, 0.1f); } } void Attack() { isAttacking = true; if (attack.clip.events.Length == 0) { // 添加攻击动画结束后的回调函数 AnimationEvent endEvent = new AnimationEvent(); endEvent.functionName = "StopAttack"; endEvent.time = attack.clip.length - 0.2f; attack.clip.AddEvent(endEvent); // 添加攻击动画中的回调函数 AnimationEvent hitEvent = new AnimationEvent(); hitEvent.functionName = "AttackHit"; hitEvent.time = 0.5f; attack.clip.AddEvent(hitEvent); } ani.Play(attack.clip.name); } void StopAttack() { isAttacking = false; } void AttackHit() { // 射线判断打击物 GameObject obj = GameObject.Find("C_BUFFBONE_GLB_CENTER_LOC"); Ray ray = new Ray(obj.transform.position, movement); RaycastHit hit; if (Physics.Raycast(ray, out hit, rayLength)) { Debug.DrawLine(ray.origin, hit.point); OnAttackHit(hit.collider.gameObject); } }}
射线碰撞检测非常有意思,如果单纯的靠网格碰撞器来检测击打物的话,至少我是没想到什么好方法,貌似网格碰撞检测都是被动的,主动的只有射线检测。这里的Trick是把射线的长度设置为刀的长度,然后发出点设为Garen的腰部,这就可以很好地检测Garen的刀是否“砍”中了物体。
另外,Delegate对象完成了UML图中的AttackHitHandler和HitEvent类的工作,因此实际上代码并没有那么复杂。什么是Delegate呢?这好比是一个公众号,任何人都可以关注它。突然某一天,公众号宣布科比退役了!如果是关注了这个公众号的人就可以立马知道这个新闻,但是每个人都可以做出不同的反应,或伤心或开心,因人而因,和公众号就没有关系了。Delegate只负责广播新闻,却不去追究新闻发布后的结果。
三、裁判类
现在玩家可以执行各种动作了,可是我还不知道我的AttackHitHandler是否能正常工作,于是赶忙写了裁判Judge类来验证一下:
using UnityEngine;using System.Collections;using Com.mygame;public class Judge : MonoBehaviour { public int count = 7;void Start () { GarenMovement.OnAttackHit += HitEvent;} void HitEvent(GameObject obj) { if(obj.tag.Contains("Ball")) { obj.SetActive(false); if (--count == 0) { MyUI.GetInstance().Display(Time.time.ToString()); } } }}
其中,UI是后来改的,没写时可以用print或Debug来测试。记得添加Tag。
四、工厂类
现在得考虑球体了,创建一个工厂来管理这些球体是个不错的方法,新建BaseCode脚本用来写单例类吧,顺便定义一个命名空间:
using UnityEngine;using UnityEngine.UI; using System.Collections;using System.Collections.Generic;using Com.mygame;namespace Com.mygame{ public class BallFactory : System.Object { static BallFactory instance; static List<GameObject> ballList; public static BallFactory GetInstance() { if (instance == null) { instance = new BallFactory(); ballList = new List<GameObject>(); } return instance; } public GameObject GetBall() { for (int i = 0; i < ballList.Count; ++i) { if (!ballList[i].activeInHierarchy) { return ballList[i]; } } GameObject newObj = GameObject.CreatePrimitive(PrimitiveType.Sphere); newObj.GetComponent<Renderer>().material.color = Color.green; newObj.tag = "Ball"; ballList.Add(newObj); return newObj; } }}考虑到回收完全可以通过ball.SetActive(false)完成,就让Judge来完成了。
五、UI
UI这么重要的东西这么可以忘掉,在命名空间内加上:
public class MyUI : System.Object { static MyUI instance; public Text mainText; public static MyUI GetInstance() { if (instance == null) { instance = new MyUI(); } return instance; } public void Display(string info) { mainText.text = info; } }
六、场景初始化
万事俱备,只欠初始了。BaseCode刚好可以用来初始化场景。Start在区域内随机位置生成7个球,并把MyUI的mainText对象赋值:
public class BaseCode : MonoBehaviour { public int balls = 7; public Text text; void Start() { MyUI.GetInstance().mainText = text; for (int i = 0; i < balls; ++i) { GameObject ball = BallFactory.GetInstance().GetBall(); ball.transform.position = new Vector3(Random.Range(-10f, 10f), 1f, Random.Range(-10f, 10f)); } }}
代码写完了(简单吧,才200行),得把它们挂载在游戏物体上,我把GarenMovement脚本挂载在了玩家Garen上,把BallFactory和BaseCode挂载在了MainCamera上,运行一下,OK!
全部代码
GarenMovement.cs
using UnityEngine;using System.Collections;public class GarenMovement : MonoBehaviour { Animation ani; AnimationState idle; AnimationState run; AnimationState attack; public float speed = 5f; Vector3 movement; Rigidbody playerRigidbody; bool isAttacking = false; float rayLength = 1.8f; public delegate void AttackHitHandler(GameObject obj); public static event AttackHitHandler OnAttackHit; void Start() { playerRigidbody = this.GetComponent<Rigidbody>(); ani = this.GetComponent<Animation>(); idle = ani["Idle"]; run = ani["Run"]; attack = ani["Attack1"]; // 默认播放站立动画 idle.wrapMode = WrapMode.Loop; ani.Play(idle.clip.name); }void FixedUpdate () { if (!isAttacking) { float h = Input.GetAxisRaw("Horizontal"); float v = Input.GetAxisRaw("Vertical"); Move(h, v); } if (Input.GetMouseButtonDown(0)) { Attack(); }} void Move(float h, float v) { if (h != 0 || v != 0) { movement.Set(h, 0f, v); // 移动玩家位置 movement = movement.normalized * speed * Time.deltaTime; playerRigidbody.MovePosition(transform.position + movement); // 旋转玩家角度 Quaternion newRotation = Quaternion.LookRotation(movement); playerRigidbody.MoveRotation(newRotation); // 播放跑步动画 run.wrapMode = WrapMode.Loop; ani.CrossFade(run.clip.name, 0.1f); } else { ani.CrossFade(idle.clip.name, 0.1f); } } void Attack() { isAttacking = true; if (attack.clip.events.Length == 0) { // 添加攻击动画结束后的回调函数 AnimationEvent endEvent = new AnimationEvent(); endEvent.functionName = "StopAttack"; endEvent.time = attack.clip.length - 0.2f; attack.clip.AddEvent(endEvent); // 添加攻击动画中的回调函数 AnimationEvent hitEvent = new AnimationEvent(); hitEvent.functionName = "AttackHit"; hitEvent.time = 0.5f; attack.clip.AddEvent(hitEvent); } ani.Play(attack.clip.name); } void StopAttack() { isAttacking = false; } void AttackHit() { // 射线判断打击物 GameObject obj = GameObject.Find("C_BUFFBONE_GLB_CENTER_LOC"); Ray ray = new Ray(obj.transform.position, movement); RaycastHit hit; if (Physics.Raycast(ray, out hit, rayLength)) { Debug.DrawLine(ray.origin, hit.point); OnAttackHit(hit.collider.gameObject); } }}
Judge.cs
using UnityEngine;using System.Collections;using Com.mygame;public class Judge : MonoBehaviour { public int count = 7;void Start () { GarenMovement.OnAttackHit += HitEvent;} void HitEvent(GameObject obj) { if(obj.tag.Contains("Ball")) { obj.SetActive(false); if (--count == 0) { MyUI.GetInstance().Display(Time.time.ToString()); } } }}
BaseCode.cs
using UnityEngine;using UnityEngine.UI; using System.Collections;using System.Collections.Generic;using Com.mygame;namespace Com.mygame{ public class MyUI : System.Object { static MyUI instance; public Text mainText; public static MyUI GetInstance() { if (instance == null) { instance = new MyUI(); } return instance; } public void Display(string info) { mainText.text = info; } } public class BallFactory : System.Object { static BallFactory instance; static List<GameObject> ballList; public static BallFactory GetInstance() { if (instance == null) { instance = new BallFactory(); ballList = new List<GameObject>(); } return instance; } public GameObject GetBall() { for (int i = 0; i < ballList.Count; ++i) { if (!ballList[i].activeInHierarchy) { return ballList[i]; } } GameObject newObj = GameObject.CreatePrimitive(PrimitiveType.Sphere); newObj.GetComponent<Renderer>().material.color = Color.green; newObj.tag = "Ball"; ballList.Add(newObj); return newObj; } }}public class BaseCode : MonoBehaviour { public int balls = 7; public Text text; void Start() { MyUI.GetInstance().mainText = text; for (int i = 0; i < balls; ++i) { GameObject ball = BallFactory.GetInstance().GetBall(); ball.transform.position = new Vector3(Random.Range(-10f, 10f), 1f, Random.Range(-10f, 10f)); } }}
- Unity3D学习笔记(7)—— 击球游戏
- Unity3d学习笔记(7)--打靶游戏
- Unity3D学习笔记(6)—— 飞碟射击游戏
- Unity3D学习笔记(10)—— 游戏序列化
- 游戏开发学习笔记——第二期:初识Unity3D
- Unity3D 学习笔记4 —— UGUI+uLua游戏框架
- Unity3D学习笔记(十三)导出游戏
- 台球技术——站位与击球(图)
- 台球技术——击球杆法(图)
- 【Unity3D游戏开发学习笔记】(一)Unity3D初认识
- 【Unity3D游戏开发学习笔记】(二)Unity3D编辑器总览
- Unity.击球游戏
- Unity.击球游戏
- Unity3D学习笔记(2)——用GUI制作井字棋游戏
- Unity3D学习笔记(4)—— 牧师和魔鬼游戏
- Unity3D学习笔记(5)—— 牧师和魔鬼游戏改进
- Unity3D学习笔记——游戏组件之Mesh(网格组件)
- 【游戏客户端开发】Unity3D 学习笔记3——Unity3D资源目录及资源读取
- 线程之间的通信
- CentOS 7 安装matplotlib
- spring源码剖析(五)利用AOP实现自定义Spring注解
- 在word文档中添加目录(包括项目符号、编号、多级列表使用方法)
- CodeForces346 C. Number Transformation II
- Unity3D学习笔记(7)—— 击球游戏
- 【BC史上最大灾难】一场被我搞砸了的BC
- Hibernate学习总结:OneToOne单向主键关联
- MyEclipse创建JavaWeb应用(配置Tomcat)
- 自己写一个httpclient(一)
- 简谈ready与load的区别
- android 硬件知识-传感器,无线电,和媒体功能的开发
- Vmware安装debian时更换iso文件方法
- hdu 1080(LCS变形)