学习视频教程 无邪:
https://space.bilibili.com/335835274/channel/detail?cid=112338
无邪 教程
画个自己的像素人物
后续是给这个人物添加动画
直接将导出的png图片拖入Unity中,创建一个2d对象
把每个导入的动作分割成单独的16*16的图片,
多选中多张图片一起拖到对象上,则可給该对象创建动画
打开动画窗口 即可看到当前对象的所有动画
在动画界面中创建 几个全局变量来控制动画切换
在脚本代码中 获取该对象的动画组件 并通过判断player的动作 方向 来设置几个全局变量的值,理论上这几个全局变量一旦切换 动画就会根据逻辑对应的切换
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public float MoveSpeed;
private Animator Myanimator;
private Rigidbody2D Myrigidbody2D;
// Start is called before the first frame update
void Start()
{
MoveSpeed = 5;
Myanimator = GetComponent<Animator>();
Myrigidbody2D = GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void Update()
{
PlayerMove();
CheckPlayerDirectory();
}
void PlayerMove()
{
float MoveDirH = Input.GetAxis("Horizontal");
float MoveDirV = Input.GetAxis("Vertical");
Vector2 vector = new Vector2(MoveDirH * MoveSpeed, MoveDirV* MoveSpeed);
Myrigidbody2D.velocity = vector;
//if (Input.GetKey(KeyCode.W))
//{
// gameObject.transform.Translate(Vector3.up * MoveSpeed * Time.deltaTime);
//}
//if (Input.GetKey(KeyCode.S))
//{
// gameObject.transform.Translate(Vector3.down * MoveSpeed * Time.deltaTime);
//}
//if (Input.GetKey(KeyCode.A))
//{
// gameObject.transform.Translate(Vector3.left * MoveSpeed * Time.deltaTime);
//}
//if (Input.GetKey(KeyCode.D))
//{
// gameObject.transform.Translate(Vector3.right * MoveSpeed * Time.deltaTime);
//}
}
void CheckPlayerDirectory()
{
if (Myrigidbody2D.velocity.x < 0)
{
Myanimator.SetBool("IsMoveLeft", true);
Debug.Log("IsMoveLeft -> true");
}
else
{
Myanimator.SetBool("IsMoveLeft", false);
}
/**************************************************************/
if (Myrigidbody2D.velocity.x > 0)
{
Myanimator.SetBool("IsMoveRight", true);
}
else
{
Myanimator.SetBool("IsMoveRight", false);
}
/**************************************************************/
if (Myrigidbody2D.velocity.y > 0)
{
Myanimator.SetBool("IsMoveUp", true);
}
else
{
Myanimator.SetBool("IsMoveUp", false);
}
/**************************************************************/
if (Myrigidbody2D.velocity.y < 0)
{
Myanimator.SetBool("IsMoveDown", true);
}
else
{
Myanimator.SetBool("IsMoveDown", false);
}
/**************************************************************/
if (Mathf.Abs( Myrigidbody2D.velocity.y ) < Mathf.Epsilon && Mathf.Abs(Myrigidbody2D.velocity.x) < Mathf.Epsilon)
{
Myanimator.SetBool("IsMoveDown", true);
}
}
向左走和向右走 可以共享同一个动画
具体做法是判断 角色的运动方向 并在脚本里面选择是否反转角色
代码如下
transform.localRotation = Quaternion.Euler(0,180,0);
最终还是决定做个横板跳跃游戏
人物绘制如下
一个会跳跃的小至尊宝,名字叫做Hx
做一个简易场景,给Hx的脚部添加Box Collider 2d组件 使得其能站在地面上
给Hx的身体部分添加Capsule Collider 2d 使其能和其他物体产生碰撞
添加脚本控制人物移动 和 判断人物当前运动状态 切换不同的动画
代码如下
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HxControl : MonoBehaviour
{
public float MoveSpeed;
public bool IsAdministrator;
private Rigidbody2D myrigibody;
private Animator myAnimator;
// Start is called before the first frame update
void Start()
{
MoveSpeed = 5;
myrigibody = GetComponent<Rigidbody2D>();
myAnimator = GetComponent<Animator>();
//IsAdministrator = false;
}
// Update is called once per frame
void Update()
{
Run();
Jump();
CheckAnimotion();
}
private void Run()
{
float MoveDir = Input.GetAxis("Horizontal");
Vector2 vector2 = new Vector2(MoveDir*MoveSpeed,myrigibody.velocity.y);
myrigibody.velocity = vector2;
}
private void Jump()
{
if (myAnimator.GetBool("IsHxJump") || myAnimator.GetBool("IsHxFly"))
{
}
else
{
if (Input.GetButtonDown("Jump"))
{
Vector2 vector2 = new Vector2(myrigibody.velocity.x, MoveSpeed * 1.4f);
myrigibody.velocity = vector2;
}
}
//确认为超级管理员 则可以随便飞
if (IsAdministrator)
{
if (Input.GetButtonDown("Jump"))
{
Vector2 vector2 = new Vector2(myrigibody.velocity.x, MoveSpeed * 1.4f);
myrigibody.velocity = vector2;
}
}
}
private void CheckAnimotion()
{
if (Mathf.Abs(myrigibody.velocity.x) < Mathf.Epsilon && Mathf.Abs(myrigibody.velocity.y) < Mathf.Epsilon)
{
myAnimator.SetBool("IsHxJump", false);
myAnimator.SetBool("IsHxRun", false);
myAnimator.SetBool("IsHxFly", false);
}
else
{
if (Mathf.Abs(myrigibody.velocity.y) > Mathf.Abs(Mathf.Epsilon))
{
if (myrigibody.velocity.y > Mathf.Epsilon)
{
myAnimator.SetBool("IsHxJump", true);
}
if (myrigibody.velocity.y < -Mathf.Epsilon)
{
myAnimator.SetBool("IsHxFly", true);
}
}
else
{
myAnimator.SetBool("IsHxFly", false);
myAnimator.SetBool("IsHxJump", false);
if (myrigibody.velocity.x < -Mathf.Epsilon)
{
myAnimator.SetBool("IsHxRun", true);
transform.localRotation = Quaternion.Euler(0, 0, 0);
}
if (myrigibody.velocity.x > -Mathf.Epsilon)
{
myAnimator.SetBool("IsHxRun", true);
transform.localRotation = Quaternion.Euler(0, 180, 0);
}
}
}
}
}
添加攻击动画
理论上来说攻击可以从任意状态触发,则动画流程图如下
攻击状态可以从any state出现,动画结束后又可以回到之前的状态
攻击动画选用Triger类型的变量触发
触发代码为
private void Acttack()
{
if (Input.GetButtonDown("Attack"))
{
myAnimator.SetTrigger("IsHxAttack");
Debug.Log("IsHxAttack");
}
}
攻击状态下添加HitBox
在Hx下新建一个空的子对象 名字叫HxAttack
在HxAttack下面 添加组件 Polygon Colloder 2d 多边形碰撞组件 取消该组件前的复选框
添加脚本:判断当攻击按键按下时 使能碰撞组件,并一定时间后禁用碰撞组件
脚本代码如下:
public int DamdgeVal;
private Animator animator;
private PolygonCollider2D polygonCollider;
// Start is called before the first frame update
void Start()
{
animator = GameObject.FindGameObjectWithTag("Player").GetComponent<Animator>();
polygonCollider = GetComponent<PolygonCollider2D>();
}
// Update is called once per frame
void Update()
{
}
void Attack()
{
if (Input.GetKeyDown("Attack"))
{
polygonCollider.enabled = true;
StartCoroutine(DisableHitBox());
}
}
IEnumerator DisableHitBox()
{
yield return new WaitForSeconds(0.6f);
polygonCollider.enabled = false;
}
攻击效果实现
创建一个物体,这里我创建了一截树干
给树干添加Box Collider 2d 碰撞检测框
/// <summary>
/// 怪物的血量
/// </summary>
public int healthVal;
/// <summary>
/// 怪物的伤害值
/// </summary>
public int damageVal;
/// <summary>
///
/// </summary>
private SpriteRenderer sr;
/// <summary>
/// the base Color of The Enemy
/// </summary>
private Color OriginColor;
脚本代码中 添加以上变量
public void Start()
{
sr = GetComponent<SpriteRenderer>();
OriginColor = sr.color;
}
获取树干的精灵组件,将原始颜色赋值给变量
public void Update()
{
if (healthVal <= 0)
{
Destroy(gameObject);
}
}
每一帧判断自身血量,若血量归零 则摧毁这个对象
public void TaskDamage(int damage)
{
this.healthVal -= damage;
Instantiate(BloodEffect,transform.position,Quaternion.identity);
FlashColor(0.3f);
}
void FlashColor(float conTime)
{
sr.color = Color.red;
Invoke("ResetColor", conTime);
}
void ResetColor()
{
sr.color = OriginColor;
}
新增public 受到攻击的函数 供外部对象调用
受到攻击后自身生命值减少/
初始化一个外部传进来的GameObject(这是外面写好的一个粒子特效,用作掉血效果)/
自身的颜色切换成Color.Red并延时0.3s后恢复原来的颜色
而在Hx代码块内 新增攻击函数如下:
private void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.CompareTag("Enemy"))
{
other.GetComponent<Enemy>().TaskDamage(DamdgeVal);
}
}
若Hx的攻击范围 进入了某个Collider内,则函数内进行判断 碰撞到的Collider是否属于Enemy标签的
如果属于 则调用该Enemy的TaskDamage()函数 即攻击完成;
瓦片绘制功能
在Aseprinte中绘制6432的图像,单个3232为一个基本单元
绘制两块地面并导出图像至Unity切割;
完毕后 在场景中创建TitleMap对象,则在Grid中存在TitleMap对象,打开Title Palette窗口,将切割好的模块拖进去,即可在场景中绘制
绘制完成后需要给场景里面的TitleMap对象添加碰撞体 TitleMap Collider 2d
并添加Composite Collider 2d组件,添加这个碰撞组件之后会自动带一个Rigibody组件,将Rigibody组件中BodyType设置为Static,表明
这个对象是一个静态的对象,不受重力作用;
这些设置完毕之后 开启工程,发现在该TitleMap移动的时候 Y方向上有个很小的速度 在正负变换,我是通过Y方向上速度的正负来判断人物是
在跳起来上升阶段 还是处于下落阶段,并更改不同的动画,所以这时候判断就不能直接大于零或者小于零,应该大于0.1或者小于-0.1;
地图绘制完毕后,发现角色可以爬墙,也就是在竖直的碰撞体上,只要你一直向着墙的方向移动,则不会掉下来,此时Y方向上的速度也为0,跳跃的条件我设置的是
Y方向上速度大于0.1或者小于-0.1,此时Y方向上速度为0,则可以再次跳跃,此时就达到的爬墙的效果,虽然是一个BUG 不过准备保留,用于进入一些隐藏的关卡;
绘制了初始场景的地图 如下:
计划这里为一个选择关卡的场景
左边第一位置为攀大场景,后续根据情况再添加;
在中间靠上的位置 设置了一个牛头随机移动,后续准备做成 在平台上击打一个对象,则开启牛头的移动,没有什么作用,隐藏彩蛋。