这次,又是一个全新的小玩意。就是把windows桌面上的图标,让他们整齐划一,变成贪吃蛇小游戏(没错,我就是和贪吃蛇杠上了)。
这个游戏,可以称之为“没有界面的贪吃蛇”。下面放上效果图。
下面介绍详细过程。
0.准备工作
- 系统:Win10
- 开发工具:Visual Studio2019
- 语言:C#
1.关键点
- 如何生成食物(动态创建桌面图标)
- 如何移动蛇身体和食物(移动桌面图标到指定位置)
创建桌面图标用到了接口IWshShortcut,具体代码如下
public void _createShortcut()
{
//选择随机图标
var rnd = new Random().Next(0, ICONS.Length);
var LinkPathName = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
var IconPath = ICONS[rnd];
var guid = Guid.NewGuid().ToString();
//获取图标名字
var name = ICONNAMES[rnd];
WshShell shell = new WshShell();
IWshShortcut link = (IWshShortcut)shell.CreateShortcut($"{LinkPathName}/{name+guid}.lnk");
link.IconLocation = IconPath;
link.TargetPath = IconPath;
link.Save();
}
而移动桌面图标则用到了WindowsAPI中的函数,代码如下
[DllImport("User32.dll", EntryPoint = "SendMessage")]
public static extern int SendMessage(int hWnd, int Msg, int wParam, int lParam);
private void _movdDeskTopIons(int i, int x, int y)
{
WindowsAPIs.SendMessage((int)_shortCut, (int)LVM_SETITEMPOSITION, i, (int)WindowsAPIs.MakeLParam(x, y));
}
解决了这两个问题,还存在一个隐藏的问题,即通过IWshShortcut创建的图标,在一定的时间之后或创建第二个图标之后,这个图标才能被移动。所以你会发现桌面左上角有一个多余的图标,那就是先将图标创建好放在左上角,当游戏需要食物时,才将该图标移动到随机位置,同时再创建一个备用图标放在左上角。
解决了这个问题,再加上贪吃蛇的基础游戏规则代码,这个游戏就可以完整独立流畅地运行了。
2.代码片段
下面贴出一些关键类代码,有需要完整工程文件或游戏执行文件的小伙伴可以私信我。
WindowsAPI类
public class WindowsAPIs
{
const int WM_MOUSEWHEEL = 0x020A; //鼠标滚轮
const int WM_LBUTTONDOWN = 0x0201;//鼠标左键
const int WM_LBUTTONUP = 0x0202;
const int WM_KEYDOWN = 0x0100;//模拟按键
const int WM_KEYUP = 0x0101;
const int MOUSEEVENTF_MOVE = 0x0001;//用于琴台鼠标移动
const int MOUSEEVENTF_LEFTDOWN = 0x0002;//前台鼠标单击
const int MOUSEEVENTF_LEFTUP = 0x0004;
const int WM_SETTEXT = 0x000C;//设置文字
const int WM_GETTEXT = 0x000D;//读取文字
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("User32.dll", EntryPoint = "SendMessage")]
public static extern int SendMessage(int hWnd, int Msg, int wParam, int lParam);
[DllImport("User32.dll", EntryPoint = "SendMessage")]
public static extern int SendMessage(int hWnd, int Msg, int wParam, string lParam);
[DllImport("user32.dll")]//在窗口列表中寻找与指定条件相符的第一个子窗口
public static extern int FindWindowEx(int hwndParent, // handle to parent window
int hwndChildAfter, // handle to child window
string className, //窗口类名
string windowName);
[DllImport("user32.DLL")]
public static extern IntPtr FindWindowEx(IntPtr hwndParent,
IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
public static IntPtr MakeLParam(int wLow, int wHigh)
{
return (IntPtr)(((short)wHigh << 16) | (wLow & 0xffff));
}
}
Snake类
public class Snake
{
//0123 左上右下
public int Direction = 2;
//组成蛇身的位置信息
public List<Pos> Body;
//蛇身宽度 食物宽度
public int Width=76;
//蛇身高度 食物高度
public int Height= 104;
//未消化的食物
private Queue<Pos> _digestion = new Queue<Pos>();
public Snake()
{
Body = new List<Pos>();
Body.Add(new Pos { x = 0, y = 0 });
}
public void TurnDirection(int direction)
{
if (Math.Abs(direction - Direction) == 2) return;
Direction = direction;
}
public void Move()
{
Pos head = new Pos
{
x=Body.First().x,
y=Body.First().y
};
switch (Direction)
{
case 0:
{
head.x -= Width;
break;
}
case 1:
{
head.y -= Height;
break;
}
case 2:
{
head.x += Width;
break;
}
case 3:
{
head.y += Height;
break;
}
}
Pos food = null;
if (_digestion.Count > 0)
{
food = _digestion.Dequeue();
food.x = Body.Last().x;
food.y = Body.Last().y;
}
for(var i=Body.Count-1;i>0;i--)
{
Body[i] =new Pos { x = Body[i - 1].x, y = Body[i - 1].y };
}
Body[0] = head;
if (food != null) Body.Add(food);
}
public void Eat(Pos food)
{
_digestion.Enqueue(food);
}
}
Map类
public class Map
{
public double Width { get; set; }
public double Height { get; set; }
public Map()
{
Width = SystemParameters.WorkArea.Width;//25 1920 76.8
Height = SystemParameters.WorkArea.Height;//10 1040 104
}
}
Game类
public class Game
{
public string[] ICONS= new string[] {
@"C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\Common7\IDE\devenv.exe",
@"D:\Program Files\Microsoft VS Code\Code.exe",
@"C:\Program Files\JetBrains\IntelliJ IDEA 2020.2.3\bin\idea64.exe",
@"D:\Program Files\Android Studio\Studio\bin\studio64.exe",
@"D:\Program Files (x86)\DingDing\DingtalkLauncher.exe",
@"D:\Program Files\YouDaoDic\Dict\YoudaoDict.exe",
@"D:\Program Files (x86)\Steam\steam.exe",
@"D:\Program Files\WeGame\wegame.exe",
@"C:\Program Files\Adobe\Adobe Photoshop CC 2017\Photoshop.exe",
};
public string[] ICONNAMES = new string[]
{
"Visual Studio","VSCode","IDEA","Android Studio","Dingtalk","YoudaoDict","Steam","WeGame","Photoshop"
};
private const uint LVM_SETITEMPOSITION = 0x1000 + 15;
private IntPtr _shortCut;
private bool _gaming=false;
private Snake _snake;
private Map _map;
private Pos _food;
public delegate void GameNotify(bool Gaming);
public GameNotify GameStoped;
public Game()
{
_map = new Map();
_shortCut = _getIconHandle();
}
public void Start()
{
_snake = new Snake();
_gaming = true;
new Thread(() => {
_createShortcut();
Thread.Sleep(1000);
while (_gaming)
{
_snake.Move();
if (_food == null)
_createFood();
if (!_checkCollision())
{
MessageBox.Show("游戏结束!");
_gaming = false;
Application.Current.Dispatcher.Invoke(GameStoped,_gaming);
break;
}
_drawBody();
Thread.Sleep(250);
}
}).Start();
}
public void TurnDirection(int direction)
{
_snake.TurnDirection(direction);
}
public void _createFood()
{
var rnd = new Random();
var x = rnd.Next(0, (int)_map.Width-_snake.Width);
var y = rnd.Next(0, (int)_map.Height-_snake.Height);
_createShortcut();
_movdDeskTopIons(_snake.Body.Count, x, y);
_food = new Pos { x = x, y = y };
}
private bool _checkCollision()
{
var head = _snake.Body.First();
//碰撞墙壁
if (head.x < 0 || head.x > _map.Width-_snake.Width)
return false;
if (head.y < 0 || head.y > _map.Height-_snake.Height)
return false;
//碰撞自身
for (var i = 1; i < _snake.Body.Count; i++)
{
if (
Math.Abs(head.x - _snake.Body[i].x) < _snake.Width
&&
Math.Abs(head.y - _snake.Body[i].y) < _snake.Height
)
{
return false;
}
}
//碰撞食物
if (
Math.Abs(head.x - _food.x) < _snake.Width
&&
Math.Abs(head.y - _food.y) < _snake.Height
)
{
_snake.Eat(new Pos { x = _food.x, y = _food.y });
_food = null;
}
return true;
}
private void _drawBody()
{
for(var i = 0; i < _snake.Body.Count; i++)
{
var pos = _snake.Body[i];
_movdDeskTopIons(i, pos.x, pos.y);
}
}
public void _createShortcut()
{
//选择随机图标
var rnd = new Random().Next(0, ICONS.Length);
var LinkPathName = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
var IconPath = ICONS[rnd];
var guid = Guid.NewGuid().ToString();
//获取图标名字
var name = ICONNAMES[rnd];
WshShell shell = new WshShell();
IWshShortcut link = (IWshShortcut)shell.CreateShortcut($"{LinkPathName}/{name+guid}.lnk");
link.IconLocation = IconPath;
link.TargetPath = IconPath;
link.Save();
}
private IntPtr _getIconHandle()
{
IntPtr vHandle = WindowsAPIs.FindWindow("Progman", "Program Manager");
vHandle = WindowsAPIs.FindWindowEx(vHandle, IntPtr.Zero, "SHELLDLL_DefView", null);
vHandle = WindowsAPIs.FindWindowEx(vHandle, IntPtr.Zero, "SysListView32", "FolderView");
return vHandle;
}
private void _movdDeskTopIons(int i, int x, int y)
{
WindowsAPIs.SendMessage((int)_shortCut, (int)LVM_SETITEMPOSITION, i, (int)WindowsAPIs.MakeLParam(x, y));
}
}