unity中单独显示选择物体

2015年01月17日 10:38 0 点赞 0 评论 更新于 2025-11-21 14:36

在许多3D游戏,如《仙剑奇侠传》《古剑奇谭》中,玩家常常需要在3D场景里选取物体,例如为我方角色添加状态、增加血量或者选择要攻击的敌人等。通常玩家使用鼠标来选择目标物体,当鼠标移至目标物体上时,该物体将显示轮廓线,以此表示物体被选中,随后可对其进行一系列操作。那么,如何在Unity3D中实现这一功能呢?

问题分解

我们可以将这个问题拆分为两个子问题:

  1. 如何确定物体是否被选中。
  2. 物体被选中后,如何清晰地传达给用户。

以下是《古剑奇谭》和《仙剑奇侠传》的战斗画面示例(此处可插入对应图片)。

问题解决

确定物体是否被选中

可以采用射线检测的方法,即从摄像机向鼠标所在位置发射射线。若射线击中游戏场景中的物体,就认为该物体被选中。

物体被选中后传达给用户

需要让物体的轮廓线显示出来,这是本文重点研究的内容。在Unity3D中,可通过Shader(着色器)来更改材质的渲染方法。Unity3D内置了6类着色器,从简单的VertexLit到复杂的带有高光的视差凹凸贴图(Parallax Bumped with Specular),共30个。具体如下:

  1. Normal:适用于不透明的物体。
  2. Transparent:适用于半透明的物体,透明度由贴图的alpha通道决定。
  3. TransparentCutOut:适用于某些部分透明、某些部分不透明的物体。
  4. Self - Illuminated:适用于需要自发光的物体。
  5. Reflective:适用于需要反射环境光的物体。
  6. Lightmapped:适用于需要添加光照贴图及相应的UV坐标数值。

一般来说,着色器定义了渲染物体的方法、材质中指定的贴图、用于渲染的顶点及片段着色程序、材质中调整的颜色以及各种数值设定。而材质决定了使用哪些贴图来渲染、使用哪些颜色渲染等。以下是我们定义的着色器代码:

Shader "Custom/BoundryShader" {
Properties {
// 定义材质的颜色为白色
_Color ("Main Color", Color) = (1,1,1,1)
// 定义材质的轮廓线为黑色
_OutlineColor ("Outline Color", Color) = (0,0,0,1) // 改变这个能改变轮廓边的颜色
// 定义线宽
_Outline ("Outline width", Range (0.0, 0.03)) = 0.001 // 改变这个能改变轮廓边的粗细
_MainTex ("Base (RGB)", 2D) = "white" { }
}
CGINCLUDE
#include "UnityCG.cginc"
struct appdata {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : POSITION;
float4 color : COLOR;
};
uniform float _Outline;
uniform float4 _OutlineColor;
v2f vert(appdata v) {
// just make a copy of incoming vertex data but scaled according to normal direction
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
float3 norm   = mul ((float3x3)UNITY_MATRIX_IT_MV, v.normal);
float2 offset = TransformViewToProjection(norm.xy);
o.pos.xy += offset * o.pos.z * _Outline;
o.color = _OutlineColor;
return o;
}
ENDCG
SubShader {
Tags { "Queue" = "Transparent" }
// note that a vertex shader is specified here but its using the one above
Pass {
Name "OUTLINE"
Tags { "LightMode" = "Always" }
Cull Off
ZWrite Off
ZTest Always
ColorMask RGB // alpha not used
// you can choose what kind of blending mode you want for the outline
Blend SrcAlpha OneMinusSrcAlpha // Normal
// Blend One One // Additive
// Blend One OneMinusDstColor // Soft Additive
// Blend DstColor Zero // Multiplicative
// Blend DstColor SrcColor // 2x Multiplicative
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
half4 frag(v2f i) :COLOR {
return i.color;
}
ENDCG
}
Pass {
Name "BASE"
ZWrite On
ZTest LEqual
Blend SrcAlpha OneMinusSrcAlpha
Material {
Diffuse [_Color]
Ambient [_Color]
}
Lighting On
SetTexture [_MainTex] {
ConstantColor [_Color]
Combine texture * constant
}
SetTexture [_MainTex] {
Combine previous * primary DOUBLE
}
}
}
SubShader {
Tags { "Queue" = "Transparent" }
Pass {
Name "OUTLINE"
Tags { "LightMode" = "Always" }
Cull Front
ZWrite Off
ZTest Always
ColorMask RGB
// you can choose what kind of blending mode you want for the outline
Blend SrcAlpha OneMinusSrcAlpha // Normal
// Blend One One // Additive
// Blend One OneMinusDstColor // Soft Additive
// Blend DstColor Zero // Multiplicative
// Blend DstColor SrcColor // 2x Multiplicative
CGPROGRAM
#pragma vertex vert
#pragma exclude_renderers gles xbox360 ps3
ENDCG
SetTexture [_MainTex] { combine primary }
}
Pass {
Name "BASE"
ZWrite On
ZTest LEqual
Blend SrcAlpha OneMinusSrcAlpha
Material {
Diffuse [_Color]
Ambient [_Color]
}
Lighting On
SetTexture [_MainTex] {
ConstantColor [_Color]
Combine texture * constant
}
SetTexture [_MainTex] {
Combine previous * primary DOUBLE
}
}
}
Fallback "Diffuse"
}

对于着色器程序的编写,我们先暂时搁置,接下来着重学习如何使用着色器实现不同的渲染效果。我们新建一个材质,并将该材质的着色器设置为上述编写的着色器(此处可插入设置着色器的图片)。

实现步骤

准备场景

准备好材质后,我们创建一个简单的场景(此处可插入简单场景的图片)。注意,此时场景中的物体没有轮廓线,因为使用的是默认材质Default - Diffuse。

动态更换材质

通过编程的方式动态更换材质,以实现不同的渲染效果。以下是编写的脚本:

using UnityEngine;
using System.Collections;

public class ShowBoundry : MonoBehaviour {
// 使用显示轮廓的简单材质
public Material mSimpleMat;
// 使用显示轮廓的高级材质
public Material mAdvanceMat;
// 默认材质
public Material mDefaultMat;

void Update () {
// 获取鼠标位置
Vector3 mPos = Input.mousePosition;
// 向物体发射射线
Ray mRay = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit mHit;
// 射线检验
if(Physics.Raycast(mRay, out mHit)) {
// Cube
if(mHit.collider.gameObject.tag == "Cube") {
// 将当前选中的对象材质换成带轮廓线的材质
mHit.collider.gameObject.renderer.material = mSimpleMat;
// 将未选中的对象材质换成默认材质
GameObject.Find("Sphere").renderer.material = mDefaultMat;
// 设置提示信息
GameObject.Find("GUIText").guiText.text = "当前选择的对象是:Cube";
}
// Sphere
if(mHit.collider.gameObject.tag == "Sphere") {
// 将当前选中的对象材质换成带轮廓线的材质
mHit.collider.gameObject.renderer.material = mSimpleMat;
// 将未选中的对象材质换成默认材质
GameObject.Find("Cube").renderer.material = mDefaultMat;
// 设置提示信息
GameObject.Find("GUIText").guiText.text = "当前选择的对象是:Sphere";
}
// Person
if(mHit.collider.gameObject.tag == "Person") {
// 由于人物模型的材质较为复杂,所以不能使用这种方法
}
}
}
}

在上述脚本中,我们指定了三种材质:适用于简单物体(如Cube等)的带轮廓线的材质、适用于复杂物体(如人物模型)的带轮廓线的材质(本文未实现)以及适用于简单物体的默认材质。其主要原理是文章开头提到的射线检验方法。将该脚本绑定到游戏场景中的物体上,设置好tag后即可运行程序。

通过本文的方法,我们可以实现在3D场景中对一个物体的选取,这种需求在游戏开发中较为常见。

作者信息

feifeila

feifeila

共发布了 3994 篇文章