标签:
在下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));
}
}
}
全部代码
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));
}
}
}
标签:
原文地址:http://blog.csdn.net/simba_scorpio/article/details/51170749