苹果手机, 图片长宽均为2的整数幂, 且是正方形, 才能用pvrtc4压缩
检查图片是否为POT:
static bool IsPowerOfTwo(int x)
{
return x > 0 && (x & (x - 1)) == 0;
}
或者
static bool IsPowerOfTwo(int x)
{
return (x & -x) == x;
}
原理看这里: https://stackoverflow.com/questions/600293/how-to-check-if-a-number-is-a-power-of-2
新手引导, 遮罩镂空, 整个遮罩不可点击, 只有镂空处能点击
把这个脚本挂在全屏遮罩上
public class RayCastHollow : MonoBehaviour, ICanvasRaycastFilter
{
public RectTransform rect; //镂空图片
bool ICanvasRaycastFilter.IsRaycastLocationValid(Vector2 screenPos, Camera eventCamera)
{
// 将镂空图范围内的事件镂空
return !RectTransformUtility.RectangleContainsScreenPoint(rect, screenPos, eventCamera);
}
}
本来用的是3D盒子碰撞体做的, 但这样做有个问题: 点空白区域的时候, 会触发检测
由于2D的Sprite没法用MashCollider, 所以用PolygonCollider2D
此处有个坑点: PolygonCollider2D只能站着, 不能平躺下, 所以相机必须以Z轴为正方向, 不能用Y轴
2D的射线检测和3D不一样, 要用Physics2D.Raycast, 不用Ray
void Update()
{
if (Input.GetMouseButtonDown(0))
{
Vector3 mousePos = Input.mousePosition;
mousePos.z = -Camera.main.transform.localPosition.z;
Vector3 worldPos = Camera.main.ScreenToWorldPoint(mousePos);
RaycastHit2D hit = Physics2D.Raycast(worldPos, Vector2.zero);
if (hit.transform != null)
{
print(hit.transform.name);
}
}
}
2d游戏, Vector3.up为前方向, 不能用LookAt或者LookRotation
已知从我指向敌人的方向向量, 计算子弹的rotation
计算角度, 角度小于90度则是向前发射, 大于90度则是向后发射
点乘, 看敌人在我的左边还是右边
用Quaternion.Euler设置角度, 只需旋转Z轴即可
-- dir是从我指向敌人的方向向量
function SetRot(dir)
-- 夹角必定小于180
local angle = Vector3.Angle(Vector3.up, dir)
local dot = Vector3.Dot(Vector3.right, dir)
-- dot>0, 说明往左发射, 所以角度为负数
if dot > 0 then
angle = -angle
end
transform.rotation = Quaternion.Euler(Vector3(0, 0, angle))
end
public static GameObject FindChild(GameObject parent, string childName)
{
if (parent.name == childName)
{
return parent;
}
if (parent.transform.childCount < 1)
{
return null;
}
GameObject obj = null;
for (int i = 0; i < parent.transform.childCount; i++)
{
GameObject go = parent.transform.GetChild(i).gameObject;
obj = FindChild(go, childName);
if (obj != null)
{
break;
}
}
return obj;
}
直接拿去挂在相机上就能跑了
using UnityEngine;
public class FPS : MonoBehaviour
{
float moveSpeed = 1;
Vector3 moveDir = Vector3.zero;
float rotSpeed = 50;
Vector3 rotDir = Vector3.zero;
float minY = -45;
float maxY = 45;
void Update()
{
Move();
Rot();
}
private void Move()
{
moveDir.x = Input.GetAxis("Horizontal");
moveDir.z = Input.GetAxis("Vertical");
transform.Translate(moveDir * Time.deltaTime * moveSpeed, Space.Self);
}
private void Rot()
{
rotDir.y += Input.GetAxis("Mouse X") * Time.deltaTime * rotSpeed;
rotDir.x -= Input.GetAxis("Mouse Y") * Time.deltaTime * rotSpeed;
rotDir.x = Mathf.Clamp(rotDir.x, minY, maxY);
transform.localEulerAngles = rotDir;
}
}
lua代码:
interval = 4
map = {1, 2, 3}
for playCount = 0, 13 do
local index = playCount // interval % #map + 1
local remain = interval - playCount % interval
print('当前地图为: ' .. map[index] .. ' 剩余刷新数: ' .. remain)
end
输出:
当前地图为: 1 剩余刷新数: 4
当前地图为: 1 剩余刷新数: 3
当前地图为: 1 剩余刷新数: 2
当前地图为: 1 剩余刷新数: 1
当前地图为: 2 剩余刷新数: 4
当前地图为: 2 剩余刷新数: 3
当前地图为: 2 剩余刷新数: 2
当前地图为: 2 剩余刷新数: 1
当前地图为: 3 剩余刷新数: 4
当前地图为: 3 剩余刷新数: 3
当前地图为: 3 剩余刷新数: 2
当前地图为: 3 剩余刷新数: 1
当前地图为: 1 剩余刷新数: 4
当前地图为: 1 剩余刷新数: 3
规则:
以下是伪代码
我的方法:
x = a ? 3 : 0
y = b ? 4 : 0
z = c ? 3 : 0
-- 放一堆小球进袋子
for x { all.add(q1) }
for y { all.add(q2) }
for z { all.add(q3) }
-- 从袋子里拿出一个
r = random( all.count )
if all[r] == q1 --> a
if all[r] == q2 --> b
if all[r] == q3 --> c
同事的方法:
x = a ? 3 : 0
y = b ? 4 : 0
z = c ? 3 : 0
-- 这一步是精髓, 分段
all[3] = { x, x+y, x+y+z }
r = random( x+y+z )
i = 0
for i<all.count, i++
{
-- 看随机数落在哪个段里
if r<all[i]
break
}
if i == 0 --> a
if i == 1 --> b
if i == 2 --> c
公钥密钥可以去https://www.bejson.com/enc/rsa/自己生一个
public class Web : MonoBehaviour
{
public static string publicKey = "";
public static string privateKey = "";
void Start()
{
CreateKeys(out privateKey, out publicKey);
string passwordOriginal = "123456";
string passswordEncrypt = RSAEncrypt(passwordOriginal);
Debug.LogError("加密后的密码为: " + passswordEncrypt);
string passwordDecrypt = RSADecrypt(passswordEncrypt);
Debug.LogError("解密后的密码为: " + passwordDecrypt);
}
//生成Xml形式的Key
public static void CreateKeys(out string PrivateKey, out string PublicKey)
{
try
{
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
PrivateKey = rsa.ToXmlString(true);//true是包含公钥和私钥
PublicKey = rsa.ToXmlString(false);//false是只包含公钥
}
catch (Exception ex)
{
throw ex;
}
}
//解密
public static string RSADecrypt(string content)
{
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.FromXmlString(privateKey);
byte[] cipherbytes = rsa.Decrypt(Convert.FromBase64String(content), false);
return Encoding.UTF8.GetString(cipherbytes);
}
//加密
public static string RSAEncrypt(string content)
{
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.FromXmlString(publicKey);
byte[] cipherbytes = rsa.Encrypt(Encoding.UTF8.GetBytes(content), false);
return Convert.ToBase64String(cipherbytes);
}
}
继承 INotifyPropertyChanged
using System.ComponentModel;
using System.Runtime.CompilerServices;
class NotifyObject : INotifyPropertyChanged
{
private int number1;
public int Number1
{
get { return number1; }
set
{
if (number1 != value)
{
number1 = value;
OnPropertyChanged();
}
}
}
//private int number2;
//private int number3;
public event PropertyChangedEventHandler PropertyChanged;
//[CallerMemberName]的作用:自动添加属性名
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
PropertyChanged += Do, 就能在属性改变时执行Do方法
void Do(object sender, PropertyChangedEventArgs property)
{
switch (property.PropertyName)
{
case "Number1":
Debug.LogError(1);
break;
case "Number2":
Debug.LogError(2);
break;
}
}
很详细的文章, 甚至写了泛型:
https://www.cnblogs.com/TianFang/p/3381484.html
需求是这样的:
17个问题, 对应17个小灯, 能显示红色或绿色来判断回答的正误, 17个小灯横着排列在屏幕上方;
因为17个都排出来不好看, 所以用了Scroll View只显示9个;
小灯要跟着我的答题序号同步移动, 比如我答第8题, 第8个小灯要居中显示;
后面有可能还要加更多题, 不止17个题.
//先把灯显示出来, 不管是17个还是1万个
private void InitJudgeLight()
{
judgeLightsList = new List<JudgeLight>();
for (int i = 0; i < Data.allQuestionsList.Count; i++)
{
JudgeLight judge = Instantiate(Resources.Load<JudgeLight>("Prefabs/ChildJudgeLight"), parentJudgeLight);
judgeLightsList.Add(judge);
//数字显示为i+1
Text[] texts = judge.transform.GetComponentsInChildren<Text>(true);
Array.ConvertAll(texts, p => p.text = (i + 1).ToString());
}
}
//答题的时候, 小灯移动
public void ShowQuestion()
{
//更新bar, 4 表示前5个题不用动, 9 是一共只能显示9个题
int nowQues = Data.GetCurrentQuestionIndex() - 4;//小于0也没关系的
//需移动的次数 = 灯总数 - 灯显示数, 比如说共有10个题, 框内仅能显示8个, 即需要移动2次
int maxQues = Data.allQuestionsList.Count - 9;
barJudgeLight.value = (float)nowQues / maxQues;
...//后面写显示问题
}
↓后面又加了些动画
public void ShowQuestion()
{
int nowQues = Data.GetCurrentQuestionIndex() - 4;
int maxQues = Data.allQuestionsList.Count - 9;
//滚动条会往前挪动一下
DOTween.To(() => barJudgeLight.value, x => barJudgeLight.value = x, (float)nowQues / maxQues, 1f).SetEase(Ease.OutBack);
//当前正在回答的问题会呼吸
int index = Data.GetCurrentQuestionIndex();
tweenJudgeLight = judgeLightsList[index].transform.DOScale(1.1f, 0.5f).SetLoops(-1, LoopType.Yoyo);
...//后面写显示问题
}
额, 如果太多了就不要全加载出来了, 如果太多就写个循环显示, 网上很多, 一搜就有;
当人物快跑时, 镜头会被拉远, 会有"跑太快, 镜头都跟不上了"的感觉
太近了的话有可能会穿模, 所以相机的最近距离给个0.01;
using UnityEngine;
public class LerpFlollow : MonoBehaviour
{
public Transform target;
private Vector3 offset;
public float moveSpeed;
void Start()
{
offset = transform.position - target.position;
}
void Update()
{
transform.position = Vector3.Lerp(transform.position, target.position + offset, Time.deltaTime * moveSpeed);
}
}
主要是Mathf.PingPong(value, max);
会从0到max, 以value增加的速度, 做来回运动
using UnityEngine;
using UnityEngine.UI;
public class ImageSparkle : MonoBehaviour
{
private Image image;
private void Start()
{
image = GetComponent<Image>();
}
private void Update()
{
//回头看看, 这里的Color应该做一个全局的, 不要每帧都new
image.color = new Color(1, 0, 0, Mathf.PingPong(Time.time, 1));
}
private void OnDestroy()
{
image.color = new Color(0, 0, 0, 0);
}
}
↓要完善的话再加上这一堆东西
public static ImageSparkle Get<T>(T t) where T : Component
{
ImageSparkle sparkle = t.gameObject.GetComponent<ImageSparkle>();
if (sparkle == null)
{
sparkle = t.gameObject.AddComponent<ImageSparkle>();
}
return sparkle;
}
public static ImageSparkle Get(GameObject obj)
{
ImageSparkle sparkle = obj.GetComponent<ImageSparkle>();
if (sparkle == null)
{
sparkle = obj.AddComponent<ImageSparkle>();
}
return sparkle;
}
public static void Remove(GameObject obj)
{
ImageSparkle sparkle = obj.GetComponent<ImageSparkle>();
if (sparkle != null)
{
Destroy(sparkle);
}
}
更简单的方法是用DOTween, 但是如果多次激活这个方法的话就不大好用了
image.DOFade(0,1).SetLoops(-1,LoopType.Yoyo);
主要是 InputField.onValueChanged.AddListener();
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class SearchTool : MonoBehaviour
{
private List<GameObject> toolsList;
public InputField input;
private void Awake()
{
input.onValueChanged.AddListener(str => Scerch(str));
toolsList = new List<GameObject>();
foreach (Transform item in transform)
{
toolsList.Add(item.gameObject);
}
}
private void Scerch(string str)
{
for (int i = 0; i < toolsList.Count; i++)
{
toolsList[i].SetActive(false);
}
List<GameObject> resultsList = toolsList.FindAll(p => p.name.Contains(str));
for (int i = 0; i < resultsList.Count; i++)
{
resultsList[i].SetActive(true);
}
}
}
↓改了一版, 节省掉了2个遍历1个列表
private void ScerchTools(string str)
{
for (int i = 0; i < toolsList.Count; i++)
{
GameObject tool = toolsList[i];
if (tool.name.Contains(str))
{
tool.SetActive(true);
}
else
{
tool.SetActive(false);
}
}
}
↓甚至可以简化成这样 (但没必要, 因为别人可能不好理解, 也不便于修改)
private void ScerchTools(string str)
{
toolsList.ForEach(p => p.SetActive(p.name.Contains(str)));
}
如果不希望"鼠标可以在View里拖动View", 那就把Scroll Rect上的Horizontal取消勾选;
using UnityEngine;
using UnityEngine.UI;
public class SliderCtrl : MonoBehaviour
{
public Scrollbar bar;
public Button btnRight;
public Button btnLeft;
private void Awake()
{
btnRight.onClick.AddListener(() => clickBtnRight());
btnLeft.onClick.AddListener(() => clickBtnLeft());
}
private void clickBtnRight()
{
bar.value += 0.5f;
}
private void clickBtnLeft()
{
bar.value -= 0.5f;
}
}
主要是用的Mathf.Lerp();
private float nextView;
private float nowView = 60;
private float minView = 12;
private float maxView = 80;
private void Update()
{
if (cameraOutUI.activeInHierarchy)
{
float speed = Input.GetAxis("Mouse ScrollWheel");
//当在滑
if (Mathf.Abs(speed) > Mathf.Abs(nextView))
{
nextView = speed;
}
//减速到0
nextView = Mathf.Lerp(nextView, 0, 5 * Time.deltaTime);
//乘法改变view
nowView *= (1 - nextView * 0.15f);
nowView = Mathf.Clamp(nowView, minView, maxView);//限制视野
camera.fieldOfView = nowView;
}
}
用的是eulerAngles
FPSController是身体, 只负责绕Y轴转动
FPSHead是头(相机), 挂在身体上, 只负责绕X轴转动
///
/// 禁用第一人称的时候, 我觉得静止的镜头很无聊, 就加了镜头随鼠标轻微转动
///
private void CameraRotateLittle()
{
if (!isFPS)
{
//当鼠标左右滑动的时候, 我们希望视角是绕Y轴旋转的
float rotY = Mathf.Clamp(FPSController.eulerAngles.y + Input.GetAxis("Mouse X") * Time.deltaTime, 120, 150);
FPSController.eulerAngles = new Vector3(0, rotY, 0);
//当鼠标上下滑动的时候, 我们希望视角是绕X轴旋转的, 注意这里↓是减法
float rotX = Mathf.Clamp(FPSHead.localEulerAngles.x - Input.GetAxis("Mouse Y") * Time.deltaTime, 5, 30);
FPSHead.localEulerAngles = new Vector3(rotX, 0, 0);
}
}
需要注意的是eulerAngles.x只能是0到180的值, 不能为负数
精髓是: 等下一帧 yield return new WaitForEndOfFrame();
public IEnumerator ShowChoosePanel()
{
float filledNum = 0;
while (filledNum < 1)
{
filledNum += Time.deltaTime;
imageFill.fillAmount = filledNum;
yield return new WaitForEndOfFrame();
}
}
注意: 写while的时候, 要么像上面↑这样在循环内有延时, 要么像下面↓这样保证能够跳出循环
while (a < 10)
{
a++;
}
//或者
while (true)
{
a++;
if(a >= 10) return;
}
如果写成下面↓这样, 编辑器会卡死
while (true){}
按住鼠标右键控制上下左右旋转, 滑轮放大缩小
if (Input.GetMouseButton(1))
{
bagPos.Rotate(new Vector3(Input.GetAxis("Mouse Y")*2, -Input.GetAxis("Mouse X"))*2, Space.World);
}
if (Input.GetAxis("Mouse ScrollWheel") != 0)
{
bagPos.position -= Vector3.forward * Input.GetAxis("Mouse ScrollWheel") * 50;
//限制放大
bagPos.localPosition = new Vector3(bagPos.localPosition.x, bagPos.localPosition.y, Mathf.Clamp(bagPos.localPosition.z, -300, 0));
}
//禁用鼠标
Cursor.lockState = CursorLockMode.Locked;//锁定鼠标, 禁止鼠标移动到游戏窗口以外
Cursor.visible = false;//鼠标不可见(仅在游戏窗口内), 如果鼠标移动到游戏视窗外, 鼠标还是可见的
//启用鼠标
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
API是这样的:
public static void SetCursor(Texture2D texture, Vector2 hotspot, CursorMode cursorMode);
参数1是鼠标样式图, 参数2是点击响应点, 一般是(0,0), 即左上角, 参数3选Auto
//用法
Cursor.SetCursor(normalCursor, new Vector2(0, 0), CursorMode.Auto);
//鼠标图的中心点
Cursor.SetCursor(normalCursor, normalCursor.texelSize / 2, CursorMode.Auto);
public static void RandomList<T>(List<T> list)
{
for (int i = 0; i < list.Count; i++)
{
int randomIndex = UnityEngine.Random.Range(i, list.Count);
T temp = list[i];
list[i] = list[randomIndex];
list[randomIndex] = temp;
}
}