【C#】List排序的方法List.Sort的使用

最近准备做一个回合制战斗的游戏,在游戏设计时,考虑到需要将所有可操作角色按照他们的速度进行排序,以来确定攻击顺序,用到了List.Sort方法,经过查看MSDN文档搞明白了怎么用,进行记录并分享。

List.Sort在默认不带参数的情况下,可以对String和Int等进行排序,而我的List中是一个类,我需要将List按照类中的Speed属性进行排序,具体方法是我们用到List.Sort 方法 (IComparer)来进行排序。

首先我们需要写一个继承自ICompare的排序函数:

/// <summary>
/// 一个针对IActItem的排序,按照Speed从大到小排序。
/// </summary>
public class SortBySpeed : IComparer<IActItem>
{
    /// <summary>
    /// 对比函数,名字不需要进行修改
    /// </summary>
    /// <param name="a">进行对比的左项</param>
    /// <param name="b">进行对比的右项</param>
    /// <returns>返回一个整形,这个整形代表左项需要移动的位置,列表顺序从左向右,-1为向左移(排在前面),1为向右移(排在后面),0为不移动</returns>
    public int Compare(IActItem a, IActItem b)
    {
        if (a == null)
        {
            if (b == null)
            {
                //如果两个都为null,则不进行排序
                return 0;
            }
            else
            {
                //a为null,b不是null,则b>a,将a向右移动1个位置
                return 1;
            }
        }
        else
        {
            if (b == null)
            {
                //b为null,a不是null,则a>b,将a向左移动1个位置
                return -1;
            }
            else
            {
                //如果a和b都不为null,则进行Speed属性对比
                if (a.Speed > b.Speed)
                {
                    //a的速度更快,排在b前面
                    return -1;
                }
                else
                {
                    //a的速度慢,则排在b后面
                    return 1;
                }
            }
        }
    }
}

之后我们只需要进行Sort调用即可:

SortBySpeed sbs = new SortBySpeed();
actList.Sort(sbs);

这样我们就完成对actList的排序,速度越大的排在越前面,按照顺序进行攻击。

更详细的使用方法可以查看MSDN文档

WordPress后台无法登录的解决办法

新买的主机,装上wordpress后,有时会出现无法登录的问题。一直没有找到原因,今天实在受不了了,分析了一下wordpress的登录代码,最终找到了解决办法。

现象:

在网址后输入 wp-admin 后,跳转到登录也,输入用户名和密码,密码正确,提交后,刷新当前页面,不进入后台(仪表盘)。

解决办法:

在博客根目录下,有一个文件 wp-login.php ,这个文件是实现登录的。在我们输入 /wp-admin 这个网址后,会默认跳到 wp-login.php 以实现登录。问题就出在这里。

用编辑器(记事本也可)打开 wp-login.php 文件,找到以下代码:

<input type="hidden" name="redirect_to" value="<?phpecho esc_attr($redirect_to);?>" />

在这行代码之前,有一行代码:

<?php } else { ?>

将其修改为:

<?php
} else {
if (!strpos($redirect_to,'index.php')){
$redirect_to = $redirect_to.'index.php';
}
?>

保存,备份原文件,将修改后的文件上传覆盖即可。

————————————————————————————————————————

以下之前在百度搜出来的解决办法,试用后无效。

登陆后台目录 打开文件/wp-includes/pluggable.php ,修改wp_set_auth_cookie函数 在pluggable.php文件找到以下代码:
setcookie($auth_cookie_name, $auth_cookie, $expire, ADMIN_COOKIE_PATH, COOKIE_DOMAIN, $secure, true);
把它替换成: setcookie($auth_cookie_name, $auth_cookie, $expire, SITECOOKIEPATH, COOKIE_DOMAIN, $secure, true);

 

Lua中协同程序coroutine与yield用法详解

Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。

而现在很多地方都会用到Lua语言,例如魔兽插件、剑网三插件、各种其他程序扩展、热更新等,所以这几天正在学习一下Lua语言,做技术储备。

而在Lua的学习过程中,大部分都很容易理解,唯独协同程序Coroutine一直理解不了。于是上网查了很多人的解释,加上测试,最终明白了其用法,分享出来,与大家交流。

本文只解释一下遇到的问题,所以你需要看过基本的Lua语法,了解基本的协程运作方式。

首先看下面的代码:

function foo (a)
    print("foo 函数输出", a)
    return coroutine.yield(2 * a) -- 返回  2*a 的值
end
 
co = coroutine.create(function (a , b)
    print("第一次协同程序执行输出", a, b) -- co-body 1 10
    local r = foo(a + 1)
     
    print("第二次协同程序执行输出", r)
    local r, s = coroutine.yield(a + b, a - b)  -- a,b的值为第一次调用协同程序时传入
     
    print("第三次协同程序执行输出", r, s)
    return b, "结束协同程序"                   -- b的值为第二次调用协同程序时传入
end)
        
print("main", coroutine.resume(co, 1, 10)) -- true, 4
print("--分割线----")
print("main", coroutine.resume(co, "r")) -- true 11 -9
print("---分割线---")
print("main", coroutine.resume(co, "x", "y")) -- true 10 end
print("---分割线---")
print("main", coroutine.resume(co, "x", "y")) -- cannot resume dead coroutine
print("---分割线---")

在协程coroutine中,当调用coroutine.yield方法时,协程coroutine会挂起,当下一次调用coroutine时会从yeild的地方继续执行。而其中让我摸不着头脑的地方,便是resume时和yield时的参数传递了。

如果coroutine.create里的函数有参数的话,那么第一次在resume中传递的参数,便是给create中函数传递的参数。

之后会执行到return coroutine.yield(2 * a),这时需要注意:协程coroutine被挂起,print会输出协程coroutine的执行结果,而且会输出coroutine.yield(2 * a)的结果。所以第一次分割线之前的输出是这样的:

第一次协同程序执行输出	1	10
foo 函数输出	2
main	true	4

之后,虽然有local r = foo(a + 1),但foo函数中的return并没有将coroutine.yield中的参数(或者说返回给resume的执行结果)返回,所以2 * a并没有赋值给r。而赋值给r的其实是第二次调用resume时传递的参数。

在第二次调用resume时,除了co还传递了一个字符串参数"r",而这个参数将会成为foo函数的返回值,并被赋值给r。resume时可以传递多个参数进去,但之前的yield中只有一个参数,其余的参数会被忽略。如果在resume时不传递参数,而yield中有参数,那么返回值则为nil,则r为nil。在这一次的yield中有两个参数,a+b和a-b,a和b是第一次调用时传递进来的参数,所以会返回他们计算的结果。

当传递参数为"r"时,第二次分割线之前的输出是这样的:

第二次协同程序执行输出	r
main	true	11	-9

搞懂了这两次的输出,后面的也就不难理解了。

当创建协程coroutine时,默认会挂起,只有在调用resume时才会被执行,当yield时会被再次挂起。第一次传递的参数会被传入create的函数中,而之后的参数传递,都是传递给yield的。并且yield中的参数会加入到resume的返回结果。

上面代码的所有输出如下:

第一次协同程序执行输出	1	10
foo 函数输出	2
main	true	4
--分割线----
第二次协同程序执行输出	r
main	true	11	-9
---分割线---
第三次协同程序执行输出	x	y
main	true	10	结束协同程序
---分割线---
main	false	cannot resume dead coroutine
---分割线---

以上分享为个人理解,如有错误欢迎指正。希望对大家有所帮助。

Unity中制作无限循环滚动的背景

在游戏中,经常会用到需要无限循环滚动的背景,例如打飞机游戏、跑酷游戏等。

本篇文章向你介绍两种简单的制作方法。

方法一:使用Material方式来制作。

优点:简单省内存。

图片导入到Unity中之后,Texture Type使用Texture。如果图片是透明的话,那么勾选Alpha Is Transparency,Wrap Mode更改为Repeat。

图片类型设置为Texture

之后创建一个Material,如果图片是透明的,则Shader选为Unlit/Transparent,如果不是的话,使用Mobile/Diffuse即可。

接下来,创建一个Quad,将材质球拖上去,然后再挂个脚本,脚本里的Update里加一句话即可:

MR.material.mainTextureOffset += new Vector2(Time.deltaTime * moveSpeed * 0.02f, 0);

方法二:两个Sprite进行无限循环。

优点:如果你在做2D游戏,而且需要使其他Sprite与背景保持同等速度会很方便。例如一个无限循环的陆地,上面会随机生成树、石头等,而树和石头需要与陆地保持同样的速度移动。

创建两个Sprite,一个默认position为0,另一个则是0加上图片的宽度,一般Pixel Per Unit为100,图片宽度为2048像素,则第二章图片的x为20.48。

两个Sprite都挂上一个Move脚本,脚本里的Update加几行代码即可:

transform.Translate(Vector3.left * Time.deltaTime * speed);
if (transform.position.x < -20.48f)
{
    transform.position += new Vector3(20.48f * 2, 0, 0);
}

两个方式各有优势,方式一只需要创建一张图片,而方式二需要两张,但是方式二在很常用,至于选择哪个,就看项目的具体需求了。

希望本分享对你有所帮助!

Unity定制Inspector常用的辅助功能

在Unity中,我们可以很方便的对Inspector进行定制,有很多简单方便的小功能都会用到。例如给一个int或float加一个范围等。这些操作不止可以在Editor类型的脚本中进行,在普通的MonoBehaviour中也可以做到。这里给大家分享一下在Unity中试验的结果。

AttributeTest.png

using UnityEngine;
using System.Collections;
using UnityEngine.UI;

//在 Add Component 按钮中增加一个菜单项
[AddComponentMenu("Transform/Follow Transform")]

//在 Create 菜单中,增加一个菜单项,前提条件是:该脚本继承自ScriptableObject
[CreateAssetMenu(fileName = "New AttributeTest File", menuName = "AttributeTest", order =1)]

//限制同一个GameObject只能有一个该组件(脚本)
[DisallowMultipleComponent]

//编辑器的编辑模式下,在Update、OnGUI、 OnRenderObject时会执行
[ExecuteInEditMode]

//自定义组件右上角?图标的链接
[HelpURL("http://fengyu.name/")]

//如果该组件继承自 MonoBehaviour,则必须有一个 BoxCollider 组件同时存在
[RequireComponent(typeof(BoxCollider))]

//没发现任何改变
[SelectionBase]

//只有在该组件继承自 StateMachineBehaviour 时有效,具体作用未知
[SharedBetweenAnimators]

public class AttributeTest : MonoBehaviour
{
    //将一个字段变为颜色原则
    [ColorUsage(true)]
    public Color color;
    
    //脚本管理的地方增加一个菜单
    [ContextMenu("Do Something")]
    void DoSomething()
    {
        Debug.Log("Perform operation");
    }
    
    //字段名称处,增加一个右键菜单。第一个参数为菜单名称,第二个参数为功能的函数名
    [ContextMenuItem("Reset", "ResetBiography")]
    [Multiline(2)]
    public string playerBiography = "";
    void ResetBiography()
    {
        playerBiography = "";
    }
    
    //该值,只有在点击Enter键、丢失焦点时才会被返回
    [Delayed]
    public float delay;
    
    //没有发现产生的影响
    [GUITarget(0, 1)]
	void OnGUI()
	{
		GUI.Label(new Rect(10, 10, 300, 100), "Visible on TV and Wii U GamePad only");
	}
    
    //用于增加一个标题头
    [Header("Header之后的部分")]
    public string header;
    
    //会在 Inspector 中隐藏字段
    [HideInInspector]
    public string hide;
    
    //创建一个显示3行的文本框
    [Multiline(3)]
    public string multiline;
    
    //使值变成滑动条的方式,并限制大小
    [Range(0, 10)]
    public float range;
    
    //加载时初始化运行函数
    [RuntimeInitializeOnLoadMethod]
    static void OnRuntimeMethodLoad()
    {
        Debug.Log("After scene is loaded and game is running");
    }
    
    //可以序列化私有字段,让 private 也在 Inspector 中显示
    [SerializeField]
    private string serializeField;
    
    //创造一个高度为10的空白区域,可以用做分割线,高度单位估计是像素
    [Space(10)]
    public string space;
    
    //创建一个文本区域,文本区域会单独一行存在
    [TextArea]
    public string textArea;
    
    //当字段获得焦点后,鼠标指向字段,会获得的提示信息
    [TooltipAttribute("这是这个字段的提示信息")]
    public string toolTip;
}

3D数学基础:向量的运算(加、减、乘)

本文摘自:《3D数学基础:图形与游戏开发》

3D数学主要关心向量和向量运算的几何意义。向量的运算包括三种:加法、减法、乘法。乘法分为三种:标量与向量相乘、向量的点乘、向量的叉乘。

向量的长度

向量的大小用向量两边加竖线表示,这和标量的“绝对值”在标量两边加单竖线类似。这种记法和n维向量大小的计算公式如下:

QQ截图20160421134354.png

如果是二维向量,则公式与勾股定理一致。

向量的加减法

向量的加减法很简单,举两例说明。

QQ截图20160421135235.png

向量的乘法

1.标量乘向量

虽然标量和向量不能相加,但能相乘。结果将得到一个向量,与原向量平行,但长度不同或方向相反。

向量v乘以一个标量k,可以想象成将向量按k倍进行缩放,如果k>0,则方向不变,如果k<0,则方向相反。

QQ截图20160421135917.png

3D数学基础:向量

本文摘自:《3D数学基础:图形与游戏开发》

向量是2D、3D数学研究的标准工具。术语向量有两种不同但相关的意义,一种是纯抽象的数学意义,另一种是几何意义。为了精通3D数学,你需要理解这两种意义以及它们之间的关系。

数学意义:对程序员而言,向量就是一个数组数学上,区分向量标量

几何意义:向量是有大小方向的线段。

  • 向量的大小就是向量的长度()。向量有非负的长度。

  • 向量的方向描述了空间中向量的指向。注意,方向并不完全和方位等同。

向量中的数表达了向量在每个维度上的有向位移。例如向量 [1,2] 表示向x轴正方向移动1,向y轴正方向移动2。向量可以理解为位移序列。

向量不描述位置,点用来描述位置。

形状和角度不变的向量,不管位置在哪里,它的值都是不变的。(你可以理解为,向量永远是相对于向量的起点来描述的,虽然这样并不太准确)

QQ截图20160421132402.png

3D数学基础:惯性坐标系

本文摘自:《3D数学基础:图形与游戏开发》

有时候,好的术语是引领人们正确理解主题的钥匙。为了简化世界坐标系到物体坐标系的转换,人们引入了一种新的坐标系,称作惯性坐标系。意思是在世界坐标系到物体坐标系的“半途”。

惯性坐标系的原点和物体坐标系的原点重合,但惯性坐标系的轴平行于世界坐标系的轴。

坐标系.png

为什么要引入惯性坐标系呢?因为从物体坐标系转换到惯性坐标系只需旋转,从惯性坐标系转换到世界坐标系只需要平移。分开考虑两件事比把它们糅合在一起容易得多。

将物体坐标系绕原点顺时针旋转45°,则会从物体坐标系转换为惯性坐标系。再从惯性坐标系向左下角平移,即可转换到世界坐标系。

3D数学基础:摄像机坐标系

本文摘自:《3D数学基础:图形与游戏开发》

摄像机坐标系是和观察者密切相关的坐标系。摄像机坐标系和屏幕坐标系相似,差别在于摄像机坐标系处于3D空间中,而屏幕坐标系在2D平面里。摄像机坐标系能被看作一种特殊的“物体”坐标系,该“物体”坐标系是定义在摄像机的屏幕可视区域。摄像机坐标系中,摄像机在原点,x轴向右,z轴向前(朝向屏幕内或摄像机方向),y轴向上(不是世界的上方,而是摄像机本身的上方)。

注意,其他书中的摄像机坐标系关于轴向的约定可能不同。特别是,许多图形学书中习惯使用右手坐标系,z轴向外,即从屏幕指向读者。

关于屏幕坐标系的典型问题是哪些物体应该在屏幕上绘制出来。

QQ截图20160418225013.png

如:

  • 3D空间中的给定点在摄像机前方吗?

  • 3D空间中的给定点是在屏幕上,还是超出了摄像机平截椎体的左、右、上、下边界?(平截椎体就是摄像机能观察到的金字塔区域)

  • 某个物体是否在屏幕上?它的部分在,或全部不在?

  • 两个物体,谁在前面?(该问题称作可见性检测)

请注意,要绘制任何物体,这些问题都是很关键的。

Unity3D《拾荒者》学习笔记(一):单例模式

最开始看到Unity官方的《拾荒者》教程时,还是初学Unity,很多知识点都不知道,如今使用Unity工作也有半年了,重新再看教程,很多地方也都能看懂了。于是重新做一下笔记,温故而知新!

单例模式

单例模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。

实现单例模式的思路是:一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称);当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。

单例模式在多线程的应用场合下必须小心使用。如果当唯一实例尚未创建时,有两个线程同时调用创建方法,那么它们同时没有检测到唯一实例的存在,从而同时各自创建了一个实例,这样就有两个实例被构造出来,从而违反了单例模式中实例唯一的原则。 解决这个问题的办法是为指示类是否已经实例化的变量提供一个互斥锁(虽然这样会降低效率)。

以上的定义,来自维基百科。

单例模式在游戏开发中是很常用的一种设计模式,下面是在Unity中的一种典型案例:

假如我们的游戏有1-N个游戏关卡(Scene),我们需要一个管理器用来对分数进行管理,在Unity中创建一个名为GameManager的GameObject,在关卡1中,我们创建了这个GameObject,并对分数进行了存储,这时,我们成功过了第一关,需要切换到第二关,切换场景时,默认会销毁当前场景的全部GameObject,而我们需要保留分数信息。这种情况下,我们就需要单例模式来实现这个功能。

public static GameManager instance = null;
void Awake()
{
    if (instance == null)
    {
        instance = this;
    }
    else if (instance != this)
    {
        Destroy(gameObject);
    }
    DontDestroyOnLoad(gameObject);
}

上面的代码就是在Unity中单例模式的完整实现。

Static:

微软的解释是:使用 static 修饰符声明属于类型本身而不是属于特定对象的静态成员。从他人的博客中我看到这样一句解释:表示此方法为所在类或所在自定义类所有,而不是这个类的实例所有。你可以通过这两句话理解Static。

在Awake中,我们首先判断该静态变量是否为null,如果为null,则将当前的类赋值给静态变量。如果不为null且不是当前类,那么说明我们目前有多个GameManager存在,此时需要销毁其他的,只保留之前使用的。通过这种方法,我们就可以通过单例模式来实现分数的管理。当然,不要忘记加上DontDestroyOnLoad,这个是让实例不在场景切换时销毁。

以上就是我对单例模式的学习与理解,如果有不对的地方,欢迎指正!